More Types
More Types
Although we use strongly type language, we often write Stringly Typed
code or rely too much on base value types like Double
, Float
, Long
, Int
, Short
, Byte
, Char
, and Boolean
.
Relying Too Much on Base Value Types
The Bad and The Ugly
- Typical case class with base value types and String.
The Good
Use Value Classes and ADTs
Scala has a good solution for the issue from relying too much on base value types. That is Value Class. So a new type can be defined without any runtime-cost as there is only internal type but no wrapper type after the code gets compiled.
For Boolean
, it is much better to use ADTs than just Boolean
. In the case class example above, the field isActive
can be re-written as an ADT.
So a possible solution for the first issue might be
newtype
Better - Use newtype is a Scala's way
to have newtype
in Haskell.
It's similar to Scala's value class.
In Scala 3 (currently Dotty), it's a language feature called opaque type
.
- Scala 2 - newtype
- Scala 2 - value class
- Scala 3
When creating a newtype for primitive types and you want them to be primitive types like int
, boolean
and double
instead of boxed-primitive like Integer
, Boolean
and Double
in runtime, use @newsubtype
.
Error Handling
The Bad and The Ugly
Throwing Stringly-typed Exception
- The ugly Stringly-typed error handling
The Good
More Types for Error Handling
Throwing an exception is bad as you can't even guess how many cases may throw exceptions so it is so hard to reason about. It would be good if a total function can be written so that you don't need to worry about the exception. Yet if it's not possible, instead of writting a partial function and let an exception be thrown, use more types to handle it properly.
- Total Function: A function defined for all possible values for the input type (i.e. for all of the domain).
- Partial Function: A function defined for some values of the input type (i.e. for some of the domain) meaning that there might be some values of the right input type that can't be handled by the function as it's not defined for those values. (e.g.
(a: Int, b: Int) => a / b
where0
is an input value for the right type of the second paramb
yet it is not a valid value forb
(not in the domain) so it throws anArithmeticException
whenb
is0
.)
Use Option
If there is only one case that can fail to get the expected result, use Option
. The one failure case does not necessarily mean it is an error. A good example might be finding an element. Let's say you're looking for a user using user's ID. The user with the given ID may or may not exist.
e.g.)
Use Either
If there are more than one case that can go wrong, use Either
. So it can be either Left
which is a failure or Right
, a successful result.
Left
may contain an error and the it can be defined as an ADT.
e.g.)