in Haskell, how to replace elements of a pair in the presence of Maybe values?

264 views Asked by At

Consider these two functions in Haskell:

replace_snd :: b -> Maybe (a, b) -> Maybe (a, b)
replace_snd y' (Just (x, y)) = Just (x, y')
replace_snd _ Nothing = Nothing

inject_snd :: Maybe b -> (a, b) -> Maybe (a, b)
inject_snd (Just b') (a, b) = Just (a, b')
inject_snd Nothing _ = Nothing

replace_snd replaces the second element of a pair, or returns Nothing if there is no pair:

> replace_snd 30 (Just (1, 2))
Just (1,30)
> replace_snd 30 Nothing
Nothing

inject_snd replaces the second element, or returns Nothing if there is no replacement:

> inject_snd (Just 30) (1, 2)
Just (1,30)
> inject_snd Nothing (1, 2)
Nothing

Also consider their symmetric counterparts replace_fst, inject_fst that act on the first element of a pair:

replace_fst :: a -> Maybe (a, b) -> Maybe (a, b)
replace_fst x' (Just (x, y)) = Just (x', y)
replace_fst _ Nothing = Nothing

inject_fst :: Maybe a -> (a, b) -> Maybe (a, b)
inject_fst (Just a') (a, b) = Just (a', b)
inject_fst Nothing _ = Nothing

My question is this: Which of these four functions can be written more compactly using built-in functions such as monadic operators? And how?

For example, I've realized that inject_snd is just (mapM . const), because Maybe is a Monad and ((,) a) is a Traversable:

> (mapM . const) (Just 30) (1, 2)
Just (1,30)
> (mapM . const) Nothing (1, 2)
Nothing

Are there similarly compact equivalents for the other three functions?

2

There are 2 answers

4
leftaroundabout On
import Control.Arrow

replaceSnd = fmap . second . const

Alternatively fmap . fmap . const, which doesn't require the Arrow import... but I dislike the (a,) functor, and this wouldn't extend to replaceFst. With Arrow however, it's just as easy:

replaceFst = fmap . first . const

The injects are a bit more awkward, but can still readily be done with an application-operator section:

injectSnd y t = fmap (($ t) . second . const) y
injectFst y t = fmap (($ t) . first . const) y

Again, you could replace second with fmap, but please don't.

This can also be written

injectSnd = flip $ \t -> fmap $ ($ t) . second . const

Of course, if you're ok with flipping the signature around, then the flip is unnecessary:

injectSnd' :: (a, b) -> Maybe b -> Maybe (a, b)
injectSnd' t = fmap $ ($ t) . second . const
2
willeM_ Van Onsem On

They can all be rewritten in a compact way. For example replace_fst (or replace_snd) is a special case of fmap :: Functor f => (a -> b) -> f a -> f b:

replace_fst :: Functor f => a -> f (a, b) -> f (a, b)
replace_fst c = fmap ((,) c . snd)

or we can work with first :: Arrow a => a b c -> a (b, d) (c, d):

import Control.Arrow(first)

replace_fst :: Functor f => a -> f (a, b) -> f (a, b)
replace_fst c = fmap (first (const c))

or more compact:

import Control.Arrow(first)

replace_fst :: Functor f => a -> f (a, b) -> f (a, b)
replace_fst = fmap . first . const

for example:

Prelude Control.Arrow> replace_fst 0 Nothing
Nothing
Prelude Control.Arrow> replace_fst 0 (Just (1,2))
Just (0,2)

as for the inject_fst, this is the same, except that we now will fmap over the first item, so:

import Control.Arrow(first)

inject_fst :: Functor f => f a -> (a, b) -> f (a, b)
inject_fst c x = fmap (($ x) . first . const) c

we thus perform an fmaping over c where, in case c is a Just y, we will return a Just (first (const y) x), and otherwise if c is a Nothing return a Nothing.

for example:

Prelude Control.Arrow> inject_fst Nothing (1,2)
Nothing
Prelude Control.Arrow> inject_fst (Just 0) (1,2)
Just (0,2)

These functions do not only work on a Maybe, but on a list [], Tree, etc.