What's important is that you can't use a value of type Option<T> as if it were T; you have to check it first. This is helpful for non-pointer types as well; I include an example of that.
Without collapsing that indirection, Option<Option<T>> is perfectly expressible:
Just (Just foo): <ptr> -> <ptr> -> foo
Just Nothing: <ptr> -> <0>
Nothing: <0>
It's true that some languages expose "nullable references" as a distinct type and not a detail of representation, and it's true (... and sometimes obnoxious) that this doesn't layer as nicely (in particular, it's not a functor).For example, `Option<i32>` is probably (the compiler gets to choose) going to be represented as two four-byte values: the discriminant, which distinguishes the `Some` and `None` cases, and then a space for the value `v`, for when the discriminant says we have `Some(v)`. Since zero is a perfectly fine value for an `i32`, we have to store the discriminant separately.
But note that this is just a flat eight-byte value. There's no heap allocation involved. It's just as if you'd written in C:
struct O { enum { Some, None } discriminant; int32_t value };
I compiled a program that uses `Option<Option<i32>>`, and looked at the DWARF debugging info to see what the compiler did with it. It seems to represent this as a twelve-byte value: four bytes for the discriminant for the outer `Option`, followed an eight-byte `Option<i32>` value laid out as before. Since you can get the address of a value held by an enum, I guess this makes sense; the compiler can't combine the discriminants or do anything clever like that.Option<i32> could have discriminant values 0 or 1, and Option<Option<i32>> could use discriminant value 2 for None, and 0 or 1 mean the object's an Option<i32>.
Edited to add: With your proposed encoding, the memory contents of an Option<Option<i32>> is identical to an Option<i32> precisely when there is an Option<i32> to speak about. And when there is no Option<i32>, you can tell that with one comparison, rather than by backing out an unknown number of levels. I like it.
I don't think so. It's just a pointer to a pointer, rather than a single pointer. That is,
int **value;
and either value == null
or *value == null
or **value == some value of interestSay you have a function, `f : (bool, T) -> Option<T>`.
function f<T>(cond : bool, value : T) : Option<T> {
if cond {
return value // equivalent: Some(value)
} else {
return null // equivalent: None
}
}
let x = f(false, f(false, 1)) // equivalent: None
let y = f(true, f(false, 1)) // equivalent: Some(None)
There is no way to tell apart `x` and `y`.Hm... not sure if I am. Or, at least, `null or T` is not a valid representation for type `Option<T>`. It's all very confusing.
> and your function is not well typed.
How so?
With AnimalMuppet's approach, you would need to return a pointer to a T (so, `&value` in something Cish). Note that this is not the same thing as a nullable reference in, for instance, C#.
`null or T` is a valid representation only when T is a non-nullable reference. It doesn't work when T is optional, and it also doesn't work when T is a primitive type, a struct, &c.
That said, there's obvious reasons it might be wanted an optimization where it's possible. For that specific case, `Just value` could be specialized to `value`, but that can be done by the compiler (akin to automatic unboxing, elsewhere).
The C++ case where a distinction between `foo = null` and `*foo = null` exists is indeed much closer to a an option type. You're right to point it out.