Better than Nothing
When we learn Result
comes with more error than Maybe
me> Wat. I found another type that has a map
function: Result
; is it one of those Functors we talked about that time? 🤔
wat> Let’s see:
Result error value
= Ok value
| Err errorResult.map : (a -> b) -> Result error a -> Result error b
wat> Looks like a legit Functor alright (assuming it respects the laws 👮♀ but I trust those nice people of the Elm core team know their business there).
me> But… it’s got two type parameters 😧 all the other Functors we saw last time had only one. How does this work?
wat> Let’s inspect the constructors first. 🔎
Result error value
= Ok value
| Err error
me> Ok, so a Result error value
has two type parameters (error
and value
) and two constructors Ok
(which takes a value
) and Err
(which takes an error
)… and it looks a bit like Maybe
with one more type parameter.
wat> It does, doesn’t it? That’s because it kinda is. Remember we can use Maybe something
to model cases when there can either be something (Just something
) or not (Nothing
), so we can use it as an output value for operations that can fail? (like converting a String
into an Int
, since all String
are not necessarily numbers)
me> That I do. 😄
wat> It’s pretty simple to have a guess as to why the conversion from a String
into an Int
failed, simply by looking at the String
, right?
me> Right.
wat> Now let’s say we want to be able to convert a String
to a fancy Email
value we just invented:
type alias Email =
{ recipient : String
, domainName : String
, domainExtension : String
}stringToEmail : String -> Maybe Email
stringToEmail input =
... -- the implementation does not really matter here :)stringToEmail "foo@bar.com" ==
Just
{ recipient = "foo"
, domainName = "bar"
, domainExtension = "com"
}stringToEmail "not @an email.com" ==
Nothing
-- that's because there are spaces in the stringstringToEmail "also@notanemail" ==
Nothing
-- that's because there's no domain extensionstringToEmail "still.not.an.email" ==
Nothing
-- that's because there's no @-- But we can't tell why it failed each time because Nothing
-- carries no information about why it failed
me> I see… there is no way to know what got wrong while trying to convert the email address.
wat> Now let’s do it with Result
instead…
stringToEmail : String -> Result String Email
stringToEmail input =
... -- again, the implementation does not really matter there ;)stringToEmail "foo@bar.com" ==
Ok
{ recipient = "foo"
, domainName = "bar"
, domainExtension = "com"
}stringToEmail "not @an email.com" ==
Err "there are spaces in the string"stringToEmail "also@notanemail" ==
Err "there is no domain extension"stringToEmail "still.not.an.email" ==
Err "there is no @"
me> 😃 Aaaaah, now the cause of the error String
can be wrapped in the Err
constructor, and we can retrieve it with a case .. of
! And when everything is fine, we can use the Ok
constructor instead and put the proper value
inside!
wat> And the error
does not even need be a String
; since it is a type parameter in Result error value
, just like value
, you can use your very own specialized error type that suits your specific needs.
me> That’s really cool! but… I’m going to need a lot of case .. of
again to use these Results
content… 😞
wat> Well, do not despair! ♘ Remember, you told me at the beginning of our chat that Result
is also a Functor, and like a good little functor, it has a map
function!
me> But I still don’t see how it works! 😠 As far as I understand, Functors are for types that have one type parameter, like Maybe something
, List something
, Array something
… right?
wat> Yes… and no. 😉
me> I mean, how can map
work with two type parameters?
-- generic form for any Functor:
-- map : (a -> b) -> Functor a -> Functor b
Maybe.map : (a -> b) -> Maybe a -> Maybe b
List .map : (a -> b) -> List a -> List b
Array.map : (a -> b) -> Array a -> Array b
wat> The trick is to include the error
parameter in the functor definition. So it’s not just Result
that is a functor, it’s really Result error
:
Result.map : (a -> b) -> Result error a -> Result error b
me> 🤯 … is that why the value
parameter is the second parameter and not the first?
wat> Yes. If not, it wouldn’t be such a good idea to name the function map
because people that use map
expect the last type parameter will be the one to be changed.
me> And I guess that Result.map
only changes the value
in an Ok
and just passes the Err
with their error
values untouched, just like Maybe.map
returns a Nothing
when fed a Nothing
?
wat> We’ll make a ducky out of you someday 👨🎓
me> Thanks… And what if I want to change not the value
, but the error
? (if any)
wat> You’ve got a nifty Result.mapError
function for that:
Result.mapError : (a -> b) -> Result a value -> Result b value
me> Oh, so it’s just like map
… but for errors.
wat> 😁 and just like Maybe
, there’s a Result.withDefault
to unwrap values (providing a default value in case we had an Err
):
Result.withDefault : value -> Result error value -> value
me> I guess I’m going to check the rest of the library out…
wat> While you’re at it, maybe you should have a peek at theResult.Extra
; it’s got extra goodies to work with Result
that you might like.