So we could have like
ourUser.getOptionalAttributes()
// could return a list [Date Birthday, Float Height, Color Eyecolor]
// or just [Color Eyecolor]
// or even an empty list
Am i totally on the wrong track here and are option types the (philosophical) correct way to express optional attributes?
The only time using a list would make sense is when a particular attribute may occur 0 to n > 1
times. I don't even know what you plan to do with the first example - that list is heterogeneous. (I'm not saying it's not possible - you could use case classes so that each list element holds either a Date, a Float, or a Color, but seriously, what does that get you?)
A Maybe
/Option
type is fundamentally the right thing to represent either Nothing
or a single value of type T
. If type T
has n
distinct values, Option <T>
has n + 1
distinct values; and being a distinct type from T
, it's an error to blindly assign an Option <T>
to a T
in the same way that it's wrong to blindly assign a 64-bit number to a 32-bit number.
For signaling failure in a computation, you could alternatively throw an exception instead of returning an Option
. This is useful when a failure is likely to be handled higher up the call stack. It's also useful when passing functions to other functions. For example, you could have a function A
that wants a function B
that returns an integer. It may be the case that B
could theoretically fail to produce an integer, but you've ensured that this particular use of B
will produce a result. Having Option
in the return type of B
would make it incompatible with A
.
Also, are there additional concepts for handling the logic of optional attributes other than nullable types and option-like types?
There's the concept of dividing values of your type into the appropriate case classes. As a simple example, suppose you're working with cartesian coordinates and need to work with both rectangular and polar coordinates. Although there's two representations, it's important to understand that they both represent values of the same type. A naive way of implementing this would be:
public class Point2D {
public static enum Type { RECT, POLAR }
public Type type;
public Option<Double> x; // Rectangular coords
public Option<Double> y; // Rectangular coords
public Option<Double> angle; // Polar coords
public Option<Double> magnitude; // Polar coords
}
But this approach is wrong - it's not that every field (save for type
) is optional, it's that there's two kinds of Point2Ds and you've conflated the two. If you modeled this correctly using case classes, none of the fields would be optional:
abstract class Point2D
case class Rect(x: double, y: double) extends Point2D
case class Polar(angle: double, magnitude: double) extends Point2D
This is more related to choosing the right model than it is to "missing" values, but I mention it since it can lead to the wrong use of Option
(or null
, god forbid.)
As others have mentioned, there's the concept of the Null Object Pattern, but that's tied to the specific semantics of the type. A null object doesn't make sense for some types.
Get(Attribute)
andHas(Attribute)
. And we'll use the birthday a lot, so add aGetBirthday
andHasBirthday
. Actually, those two calls aren't atomic and could cause issues, so just haveGetBirthday
, but have it return a list with either a Birthday or no elements. Actually, checking for no elements feels a little hacky, so always use the same empty list for your nothings, and call it Nothing. Hang on, you've invented Option :) You're actually kinda on the right lines, Option/Maybe types are best viewed as collectionsHas(Birthday)
and get yes, a fraction of a second before the user's request to remove their birthday goes through. I callGet(Birthday)
and have an exception thrown in my face. Whoops.{has(...); get(...)}
somehow atomic like when working with databases?has(x)
block, must all of that then be atomic? The performance hit would be appreciable. Returning either the value you want or some special sentinel value is a much saner way of doing it, because you sidestep that entire problem. Optional is an improvement over null because it has more useful semantics, unlike null which has the semantics of "will crash your application if you do anything with it".