Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to elegantly represent this pattern in Haskell?

Mind the pure function below, in an imperative language:

def foo(x,y):     x = f(x) if a(x)     if c(x):          x = g(x)     else:         x = h(x)     x = f(x)     y = f(y) if a(y)     x = g(x) if b(y)     return [x,y] 

That function represents a style where you have to incrementally update variables. It can be avoided in most cases, but there are situations where that pattern is unavoidable - for example, writing a cooking procedure for a robot, which inherently requires a series of steps and decisions. Now, imagine we were trying to represent foo in Haskell.

foo x0 y0 =     let x1 = if a x0 then f x0 else x0 in     let x2 = if c x1 then g x1 else h x1 in     let x3 = f x2 in     let y1 = if a y0 then f y0 else y0 in     let x4 = if b y1 then g x3 else x3 in     [x4,y1] 

That code works, but it is too complicated and error prone due to the need for manually managing the numeric tags. Notice that, after x1 is set, x0's value should never be used again, but it still can. If you accidentally use it, that will be an undetected error.

I've managed to solve this problem using the State monad:

fooSt x y = execState (do     (x,y) <- get     when (a x) (put (f x, y))     (x,y) <- get     if c x          then put (g x, y)          else put (h x, y)     (x,y) <- get     put (f x, y)     (x,y) <- get     when (a y) (put (x, f y))     (x,y) <- get     when (b y) (put (g x, x))) (x,y) 

This way, need for tag-tracking goes away, as well as the risk of accidentally using an outdated variable. But now the code is verbose and much harder to understand, mainly due to the repetition of (x,y) <- get.

So: what is a more readable, elegant and safe way to express this pattern?

Full code for testing.

like image 804
MaiaVictor Avatar asked Aug 03 '14 17:08

MaiaVictor


People also ask

What is monads in Haskell?

What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

What is a monadic function?

A monadic function is a function with a single argument, written to its right. It is one of three possible function valences; the other two are dyadic and niladic. The term prefix function is used outside of APL to describe APL's monadic function syntax.

What does () mean in Haskell?

() is very often used as the result of something that has no interesting result. For example, an IO action that is supposed to perform some I/O and terminate without producing a result will typically have type IO () .

Do notation monad Haskell?

Any instance of the Monad class can be used in a do-block in Haskell. In short, the do notation allows you to write monadic computations using a pseudo-imperative style with named variables. The result of a monadic computation can be "assigned" to a variable using a left arrow <- operator.


2 Answers

Your goals

While the direct transformation of imperative code would usually lead to the ST monad and STRef, lets think about what you actually want to do:

  1. You want to manipulate values conditionally.
  2. You want to return that value.
  3. You want to sequence the steps of your manipulation.

Requirements

Now this indeed looks first like the ST monad. However, if we follow the simple monad laws, together with do notation, we see that

do     x <- return $ if somePredicate x then g x                                     else h x    x <- return $ if someOtherPredicate x then a x                                          else b x 

is exactly what you want. Since you need only the most basic functions of a monad (return and >>=), you can use the simplest:

The Identity monad

foo x y = runIdentity $ do     x <- return $ if a x then f x                          else x     x <- return $ if c x then g x                          else h x     x <- return $ f x      y <- return $ if a x then f y                          else y     x <- return $ if b y then g x                          else y     return (x,y) 

Note that you cannot use let x = if a x then f x else x, because in this case the x would be the same on both sides, whereas

x <- return $ if a x then f x                       else x 

is the same as

(return $ if a x then (f x) else x) >>= \x -> ... 

and the x in the if expression is clearly not the same as the resulting one, which is going to be used in the lambda on the right hand side.

Helpers

In order to make this more clear, you can add helpers like

condM :: Monad m => Bool -> a -> a -> m a condM p a b = return $ if p then a else b 

to get an even more concise version:

foo x y = runIdentity $ do     x <- condM (a x) (f x) x     x <- fmap f $ condM (c x) (g x) (h x)         y <- condM (a y) (f y) y     x <- condM (b y) (g x) x     return (x , y) 

Ternary craziness

And while we're up to it, lets crank up the craziness and introduce a ternary operator:

(?) :: Bool -> (a, a) -> a b ? ie = if b then fst ie else snd ie  (??) :: Monad m => Bool -> (a, a) -> m a (??) p = return . (?) p  (#) :: a -> a -> (a, a) (#) = (,)  infixr 2 ?? infixr 2 # infixr 2 ?  foo x y = runIdentity $ do     x <- a x ?? f x # x     x <- fmap f $ c x ?? g x # h x     y <- a y ?? f y # y     x <- b y ?? g x # x     return (x , y) 

But the bottomline is, that the Identity monad has everything you need for this task.

Imperative or non-imperative

One might argue whether this style is imperative. It's definitely a sequence of actions. But there's no state, unless you count the bound variables. However, then a pack of let … in … declarations also gives an implicit sequence: you expect the first let to bind first.

Using Identity is purely functional

Either way, the code above doesn't introduce mutability. x doesn't get modified, instead you have a new x or y shadowing the last one. This gets clear if you desugar the do expression as noted above:

foo x y = runIdentity $       a x ?? f x # x   >>= \x ->       c x ?? g x # h x >>= \x ->       return (f x)     >>= \x ->       a y ?? f y # y   >>= \y ->       b y ?? g x # x   >>= \x ->       return (x , y) 

Getting rid of the simplest monad

However, if we would use (?) on the left hand side and remove the returns, we could replace (>>=) :: m a -> (a -> m b) -> m b) by something with type a -> (a -> b) -> b. This just happens to be flip ($). We end up with:

($>) :: a -> (a -> b) -> b ($>) = flip ($)      infixr 0 $> -- same infix as ($)  foo x y = a x ? f x # x   $> \x ->           c x ? g x # h x $> \x ->           f x             $> \x ->           a y ? f y # y   $> \y ->           b y ? g x # x   $> \x ->           (x, y) 

This is very similar to the desugared do expression above. Note that any usage of Identity can be transformed into this style, and vice-versa.

like image 79
Zeta Avatar answered Oct 03 '22 20:10

Zeta


The problem you state looks like a nice application for arrows:

import Control.Arrow  if' :: (a -> Bool) -> (a -> a) -> (a -> a) -> a -> a if' p f g x = if p x then f x else g x  foo2 :: (Int,Int) -> (Int,Int) foo2 = first (if' c g h . if' a f id) >>>        first f >>>        second (if' a f id) >>>        (\(x,y) -> (if b y then g x else x , y)) 

in particular, first lifts a function a -> b to (a,c) -> (b,c), which is more idiomatic.

Edit: if' allows a lift

import Control.Applicative (liftA3)  -- a functional if for lifting if'' b x y = if b then x else y  if' :: (a -> Bool) -> (a -> a) -> (a -> a) -> a -> a if' = liftA3 if'' 
like image 26
Franky Avatar answered Oct 03 '22 19:10

Franky