Okay, what I really wanted to do is, I have an Array and I want to choose a random element from it. The obvious thing to do is get an integer from a random number generator between 0 and the length minus 1, which I have working already, and then applying Array.get, but that returns a Maybe a
. (It appears there's also a package function that does the same thing.) Coming from Haskell, I get the type significance that it's protecting me from the case where my index was out of range, but I have control over the index and don't expect that to happen, so I'd just like to assume I got a Just
something and somewhat forcibly convert to a
. In Haskell this would be fromJust
or, if I was feeling verbose, fromMaybe (error "some message")
. How should I do this in Elm?
I found a discussion on the mailing list that seems to be discussing this, but it's been a while and I don't see the function I want in the standard library where the discussion suggests it would be.
Here are some pretty unsatisfying potential solutions I found so far:
- Just use withDefault. I do have a default value of
a
available, but I don't like this as it gives the completely wrong meaning to my code and will probably make debugging harder down the road. - Do some fiddling with ports to interface with Javascript and get an exception thrown there if it's Nothing. I haven't carefully investigated how this works yet, but apparently it's possible. But this just seems to mix up too many dependencies for what would otherwise be simple pure Elm.
(answering my own question)
I found two more-satisfying solutions:
- Roll my own partially defined function, which was referenced elsewhere in the linked discussion. But the code kind of feels incomplete this way (I'd hope the compiler would warn me about incomplete pattern matches some day) and the error message is still unclear.
Pattern-match and use Debug.crash if it's a Nothing. This appears similar to Haskell's
error
and is the solution I'm leaning towards right now.import Debug fromJust : Maybe a -> a fromJust x = case x of Just y -> y Nothing -> Debug.crash "error: fromJust Nothing"
(Still, the module name and description also make me hesitate because it doesn't seem like the "right" method intended for my purposes; I want to indicate true programmer error instead of mere debugging.)
Solution
The existence or use of a fromJust
or equivalent function is actually code smell and tells you that the API has not been designed correctly. The problem is that you're attempting to make a decision on what to do before you have the information to do it. You can think of this in two cases:
If you know what you're supposed to do with
Nothing
, then the solution is simple: usewithDefault
. This will become obvious when you're looking at the right point in your code.If you don't know what you're supposed to do in the case where you have
Nothing
, but you still want to make a change, then you need a different way of doing so. Instead of pulling the value out of theMaybe
useMaybe.map
to change the value while keeping theMaybe
. As an example, let's say you're doing the following:foo : Maybe Int -> Int foo maybeVal = let innerVal = fromJust maybeVal in innerVal + 2
Instead, you'll want this:
foo : Maybe Int -> Maybe Int foo maybeVal = Maybe.map (\innerVal -> innerVal + 2) maybeVal
Notice that the change you wanted is still done in this case, you've simply not handled the case where you have a
Nothing
. You can now pass this value up and down the call chain until you've hit a place where it's natural to usewithDefault
to get rid of theMaybe
.
What's happened is that we've separated the concerns of "How do I change this value" and "What do I do when it doesn't exist?". We deal with the former using Maybe.map
and the latter with Maybe.withDefault
.
Caveat
There are a small number of cases where you simply know that you have a Just
value and need to eliminate it using fromJust
as you described, but those cases should be few and far between. There's quite a few that actually have a simpler alternative.
Example: Attempting to filter a list and get the value out.
Let's say you have a list of Maybe
s that you want the values of. A common strategy might be:
foo : List (Maybe a) -> List a
foo hasAnything =
let
onlyHasJustValues = List.filter Maybe.isJust hasAnything
onlyHasRealValues = List.map fromJust onlyHasJustValues
in
onlyHasRealValues
Turns out that even in this case, there are clean ways to avoid fromJust
. Most languages with a collection that has a map and a filter have a method to filter using a Maybe
built in. Haskell has Maybe.mapMaybe
, Scala has flatMap
, and Elm has List.filterMap
. This transforms your code into:
foo : List (Maybe a) -> List a
foo hasAnything =
let
onlyHasRealValues = List.filterMap (\x -> x) hasAnything
in
onlyHasRealValues
来源:https://stackoverflow.com/questions/28699800/right-way-to-forcibly-convert-maybe-a-to-a-in-elm-failing-clearly-for-nothings