fromMaybe is Just a fold
fromMaybe
is a useful function. Maybe
is not guaranteed to hold a value, but you can always get one by providing a default to fallback on:
fromMaybe :: a -> Maybe a -> a
= case m_x of
fromMaybe dfault m_x Just x -> x
Nothing -> dfault
At the interactive Haskell shell, it behaves exactly as intended:
ghci> fromMaybe "default" (Just "a string")
"a string"
ghci> fromMaybe "default" Nothing
"default"
It’s a bit wordy. If I am working with Maybe
-heavy code, I sometimes alias this function to the operator //
.1
// y = fromMaybe y x x
or the point-free style:
//) = flip fromMaybe (
And now:
ghci> :type (//)
(//) :: Maybe c -> c -> c
ghci> Just "one little fox" // "no animals here!"
"one little fox"
ghci> Nothing // "no animals here!"
"no animals here!"
So concise!
enter Either
Well today I was converting some Maybe
code to use Either ErrorCode
. This is not difficult — the strong type system makes it a pretty mechanical process. I replaced fromMaybe
with this definition of fromEitherE
2:
fromEitherE :: a -> Either e a -> a
= case e_x of
fromEitherE dfault e_x Right x -> x
Left _ -> dfault
Normally this would entail replacing the calls to fromMaybe
all over the place, but since I had been using the //
alias everywhere, that was all I needed to change:
//) = flip fromEitherE (
To show that it works:
ghci> Right "my cool home page" // "server error"
"my cool home page"
ghci> Left 418 // "server error"
"server error"
We get the Right
value if there is one, and if not, ignore the error code and use the provided fallback value.
Sure, this works, but somehow I am dissatisfied. I expected to find a polymorphic solution that can handle both cases cleanly. After all, look at the similarities in their types:
fromMaybe :: a -> Maybe a -> a
fromEitherE :: a -> Either e a -> a
I wondered whether there were any other functors which exhibit the same pattern.
lists
We can imagine extracting the first element of a list, or using a fallback if the list is empty!3
fromList :: a -> [a] -> a
= case l of
fromList dfault l :rest -> first
first-> dfault
[]
//) = flip fromList (
ghci> [] // "no trains :v("
"no trains :v("
ghci> ["3pm"] // "no trains :v("
"3pm"
ghci> ["3pm","7pm"] // "no trains :v("
"3pm"
It is in this form that a solution becomes the most clear. We are reducing a list to a single element, an operation which shares its name with a certain bread-making technique.
folding
A fold
, in the Lisp tradition, is a function which takes a combining function, a starting value, and a list. It uses the function and starting value to walk through the list, accumulating as it goes along.
fold :: (a -> a -> a) -> a -> [a] -> a
= recurse list
fold f start list where
= case l of
recurse l -> start
[] :rest -> f first (recurse rest) first
ghci> fold (+) 0 [1,2,3,4,5]
15
We could use fold
to implement our fromList
function — we just need a function which always returns its first argument!
const :: a -> b -> a
const x y = x
ghci> fold const 0 [1,2,3]
1
ghci> fold const 0 []
0
But lists are far from the only structures that we can fold. The Foldable
type-class exists to capture the pattern of types which can be folded in some way using a function. Common examples include lists, trees, and sets.
The general version is called foldr
and looks like this:
ghci> :info Foldable
class Foldable t where
foldr :: (a -> b -> b) -> b -> t a -> b
length :: t a -> Int
-- and many more...
instance Foldable Set -- Defined in ‘Data.Set.Internal’
instance Foldable [] -- Defined in ‘Data.Foldable’
instance Foldable NonEmpty -- Defined in ‘Data.Foldable’
instance Foldable Maybe -- Defined in ‘Data.Foldable’
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
Wait. Instance Foldable Maybe
? Yes!
ghci> length Nothing
0
ghci> length (Just undefined)
1
ghci> foldr (+) 2 (Just 2)
4
It’s true! We can fold both Maybe
s and Either a
s. This suggests a polymorphic solution to our puzzle:
(//) :: Foldable f => f a -> a -> a
//) = flip (foldr const) (
ghci> Just "ok" // "otherwise"
"ok"
ghci> Right "ok" // "otherwise"
"ok"
ghci> ["ok"] // "otherwise"
"ok"
And there was much rejoicing.
but…
One more thing.
(//)
does not make sense for every Foldable
. NonEmpty
lists, for example, are guaranteed to hold at least one value. If we want to get the first value out of a NonEmpty
, we always can! There’s no need for an “otherwise” value.
We can avoid (//)
on such types by defining our own sub-class of Foldable
:
class Foldable f => Optional f where
(//) :: f a -> a -> a
//) = flip (foldr const)
(
instance Optional Maybe
instance Optional (Either e)
instance Optional []
cool, that’s it
This post was inspired by a discussion in the #haskell irc channel on libera.chat, with special thanks to geekosaur and hpc. I hope you found it interesting too.
All the code examples above are available on GitLab and GitHub.
more from the friends of danso:
ETS Match Specification Pitfall: Maps
February 9During my work, I had a problem where I needed to efficiently write tree-shaped data. I also needed the ability to query ranges of the data. I reached for ETS tables, using the ordered_set table setting, and relied on match specifications to read ranges from the…
via Richard KallosDan Savage on the Recent Inauguration
January 30I've been reading and listening to Dan Savage for decades. He's a sex & advice columnist that started out in the back pages of a Seattle Weekly but who's grown in reach and popularity dramatically over the years. He spearheaded oppositio…
via Searching For TaoDevelopment of rustc_codegen_gcc #2
January 30A couple months ago, I wrote an article that described the challenges we had to do the previous sync from the Rust repo to rustc_codegen_gcc. (if you don’t know what a sync is, please read that blog article). We also had many...
via Antoyo's Bloggenerated by openring