Reputation: 119
So, I have been doing the Elm track on Exercism.org and I just finished the exercise about the Maybe
concept, but one thing is not clear to me yet. What is the purpose of the Just
in the definition of Maybe
?
type Maybe a = Nothing | Just a
For example, what's the difference between Int
and Just Int
and why an integer is not considered a Just Int
if I don't add the Just
word before?
More concretely, when I was trying to solve the RPG problem my first trying resulted in something like this:
type alias Player =
{ name : Maybe String
, level : Int
, health : Int
, mana : Maybe Int
}
revive : Player -> Maybe Player
revive player =
case player.health of
0 ->
if player.level >= 10 then
Player player.name player.level 100 100
else
Player player.name player.level 100 Nothing
_ ->
Nothing
Just to find out that my mistake was in the if statement, that should return Just Person
, i.e.:
if player.level >= 10 then
Just (Player player.name player.level 100 (Just 100))
else
Just (Player player.name player.level 100 Nothing)
Upvotes: 5
Views: 766
Reputation: 70277
If you're coming from a background of dynamic typing like Python then it's easy to see it as pointless. In Python, if you have an argument and you want it to be either an integer or empty, you pass either an integer or None
. And everyone just understands that None
is the absence of an integer.
Even if you're coming from a poorly-done statically typed language, you may still see it as odd. In Java, every reference datatype is nullable, so String
is really "eh, there may or may not be a String
here" and MyCustomClass
is really "eh, there may or may not really be an instance here". Everything can be null
, which results in everyone constantly checking whether things are null
at every turn.
There are, broadly speaking, two solutions to this problem: nullable types and optional types. In a language like Kotlin with nullable types, Int
is the type of integers. Int
can only contain integers. Not null
, not a string, not anything else. However, if you want to allow null
, you use the type Int?
. The type Int?
is either an integer or a null
value, and you can't do anything integer-like with it (such as add it to another integer) unless you check for null
first. This is the most straightforward solution to the null
problem, for people coming from a language like Java. In that analogy, Int
really is a subtype of Int?
, so every integer is an instance of Int?
. 3
is an instance of both Int
and Int?
, and it means both "this is an integer" and also "this is an integer which is optional but exists".
That approach works fine in languages with subtyping. If your language is built up from a typical OOP hierarchy, it's easy to say "well, T
is clearly a subtype of T?
" and move on. But Elm isn't built that way. There's no subtyping relationships in Elm (there's unification, which is a different thing). Elm is based on Haskell, which is built up from the Hindley-Milner model. In this model, every value has a unique type.
Whereas in Kotlin, 3
is an instance of Int
, and also Int?
, and also Number
, and also Number?
, and so on all the way up to Any?
(the top type in Kotlin), there is no equivalent in Elm. There is no "top type" that everything inherits from, and there is no subtyping. So it's not meaningful to say that 3
is an instance of multiple types. In Elm, 3
is an instance of Int
. End of story. That's it. If a function takes an argument of type Int
, it must be an integer. And since 3
can't be an instance of some other type, we need another way to represent "an integer that may or may not be there".
type Maybe a = Nothing | Just a
Enter optional typing. 3
can't be an optional integer, since it's an Int
and nothing else. But Just 3
, on the other hand... Just 3
is an entirely different value and its type is Maybe Int
. A Just 3
is only valid in situations where an optional integer is expected, since it's not an Int
. Maybe a
is what's called an optional type; it's a completely separate type which represents the type a
, but optional. It serves the same purpose and T?
in a language like Kotlin, but it's built up from different foundations.
Getting into which one is better would derail this post, and I don't think that's important here. I have my opinions, but others have theirs as well. Optional typing and nullable typing are two different approaches to dealing with values that may or may not exist. Elm (and Haskell-like languages) use one, and other languages might use the other. A well-rounded programmer should become comfortable with both.
Upvotes: 11
Reputation: 12747
type Maybe a = Nothing | Just a
---
Just 123 -- is a `Maybe Int`
Means Maybe
is a type with an associated generic type a
. Similar to C++'s T
template <class T>
class Maybe
using MaybeInt = Maybe<Int>
All Nothing
and Just a
are are functions (aka constructors) to make a Maybe
. In Python it might look like:
def Nothing() -> Maybe:
return Maybe() # except in elm, it knows the returned Maybe came from a
# Nothing, so there's some machinery missing here
def Just(some_val) -> Maybe:
return Maybe(some_val)
So if a function returns a Maybe, the returned value has to be passed through one of the two Nothing
or Just
constructors.
Upvotes: 0
Reputation: 25909
Just in Elm is a tag but in this context you can think of it like a function that takes a value of type Int
, and return something of the type Maybe Int
.
Upvotes: 0
Reputation: 664538
Why an integer is not considered a
Just Int
if I don't add theJust
word before?
Simply because without the constructor (Just
), it's only an integer and not something else. There's no automatic type conversion, you have to be explicit about what you want. Would you also consider allow writing 100
if you meant the single-element list [100]
? Soon, you would have no idea what it meant if someone wrote 100
.
This is not specific to Maybe
and its Just
variant, this is the rule for all data types. There is no exception for Maybes, even if the language is confusing - an Int
is just an Int
, but not a Just Int
.
Upvotes: 3