Is there a point-free way to convert a conditional

2019-04-28 07:28发布

I am just working through some simple exercises in haskell and was wondering if there was a point-free way of converting an if-then-else statement into a Maybe type: Nothing being returned if the condition is false, and Just the input if the condition is true.

In short, given some:

maybeIf :: (a -> Bool) -> a -> Maybe a
maybeIf cond a = if cond a then Just a else Nothing

Is there an implementation that is point-free with respect to a? I've also been looking at a more concrete version, a -> Maybe a, and feel like there may be an answer somewhere in Control.Arrow. However, since Maybe is a data type and if-else statements control data flow, I'm unsure if there is a clean way of doing it.

5条回答
三岁会撩人
2楼-- · 2019-04-28 08:10

If we choose a Church-encoding for Booleans…

truth :: Bool -> a -> a -> a
truth True  t f = t
truth False t f = f

Then we can write a point-free maybeIf in Applicative-style.

maybeIf :: (a -> Bool) -> a -> Maybe a
maybeIf = liftA3 truth <*> pure Just <*> pure (pure Nothing)

Some intuitions…

f <$> m₁ <*> … <*> mₙ = \x -> f (m₁ x) … (mₙ x)
liftAₙ f <$> m₁ <*> … <*> mₙ = \x -> f <$> m₁ x <*> … <*> mₙ x

Here is a rendering in PNG format of the above "intuitions", in case your installed fonts do not support the needed unicode characters.

enter image description here

So therefore:

liftA3 truth <*> pure Just <*> pure (pure Nothing)
= liftA3 truth <$> id <*> pure Just <*> pure (pure Nothing)
= \p -> truth <$> id p <*> (pure Just) p <*> (pure (pure Nothing)) p
= \p -> truth <$> p <*> Just <*> pure Nothing
= \p -> \a -> truth (p a) (Just a) ((pure Nothing) a)
= \p -> \a -> truth (p a) (Just a) Nothing
查看更多
迷人小祖宗
3楼-- · 2019-04-28 08:16

This function is defined in Control.Monad.Plus and is called partial

查看更多
趁早两清
4楼-- · 2019-04-28 08:19

You can import find from Data.Foldable and then it's quite simply:

import Data.Foldable(find)

maybeIf cond = find cond . Just

The function find is not complicated so you could quite easily define it yourself less generically, in terms of Maybe, but it isn't actually so different from your own implementation of maybeIf so you might not gain much, depending on why you wanted to do it.

查看更多
祖国的老花朵
5楼-- · 2019-04-28 08:20

Following dfeuer's lead (and using Daniel Wagner's new name for this function),

import Data.Bool (bool)
--         F    T    
-- bool :: a -> a -> Bool -> a

ensure :: (a -> Bool) -> a -> Maybe a
ensure p x = bool (const Nothing) Just (p x) x

ensure p   = join (bool (const Nothing) Just . p)
           = bool (const Nothing) Just =<< p  

ensure     = (bool (const Nothing) Just =<<)

join is a monadic function, join :: Monad m => m (m a) -> m a, but for functions it is simply

join   k  x = k x x
(k =<< f) x = k (f x) x  

join is accepted as a replacement for W combinator in point-free code.

You only wanted it point-free with respect to the value argument, but it's easy to transform the equation with join further (readability of the result is another issue altogether), as

          = join ((bool (const Nothing) Just .) p)
          = (join . (bool (const Nothing) Just .)) p

Indeed,

 #> (join . (bool (const Nothing) Just .)) even 3
Nothing

 #> (bool (const Nothing) Just =<<) even 4
Just 4

But I'd much rather see \p x -> listToMaybe [x | p x] in an actual code.

Or just \p x -> [x | p x], with Monad Comprehensions. Which is the same as Daniel Wagner's x <$ guard (p x), only with different syntax.

查看更多
祖国的老花朵
6楼-- · 2019-04-28 08:29

The main thing getting in the way of making that pointfree is the if/then/else. You can define an if' combinator, or you can use this generalized version that I define and use often:

ensure p x = x <$ guard (p x)

Standard tools give successive point-free versions as

ensure p = ap (<$) (guard . p)
ensure = ap (<$) . (guard .)

though I really don't think either are better than the pointful version.

查看更多
登录 后发表回答