🦀old:
let foo: Option<i8> = Some(6);
🐍bold:
foo: Optional[int] = 6a realization inspired by seeing Dhall in practice
Consider any polymorphic function that returns an option, say, a function for safely indexing into a list.
index<A> : (List<A>, Int) -> Option<A>
This function looks up a value at a given index and returns it (possibly wrapped in a Some) or returns null/None if the index is out of range, so it should be correct with either definition of Option, right?
No it's not.
index<Option<Int>>([0, null, 2], 1)
This returns null, so we can conclude that index 1 is out of range for the list, i.e. the list has at most one element. Huh.
The issue with null unions like this is that the result type of this function is
Option<Option<Int>>
= Int | null | null
= Int | null
Without a Some tag, there is no way to differentiate between None and Some(None). Was the index really out of range or was there just a null at the index? Dynamically typed langauges run into this issue all the time.
The linked Dhall code might be a little verbose, but it is correct and I will take that over brevity any day of the week.
I feel a bit bad advertising it here, but uhhh here it is: https://ihatereality.space/01-why-null-sucks/