2

I have the following code for an API for manipulating a robot:

data Direction = Left | Right
forward    :: IO ()
blocked :: IO Bool
turn       :: Direction -> IO ()

I am trying to understand two programs that will move the robot forward unless it is blocked by an obstacle, in which case, the robot should turn in the Right direction.

However, I am not sure what is the difference is between the following two programs:

-- program 1
robot = do
  detected <- blocked
  if detected 
    then turn Right
    else forward
  robot

-- program 2
robot = do
  detected <- blocked
  if detected
    then turn Right
         robot
    else forward
         robot

The line detected <- blocked takes the boolean value out of the IO. If the condition if detected evaluates as true, then the robot turns Right, otherwise the robot moves forward. In program 1 the function robot is called again after moving the robot either right or forward. In program 2, the function robot is called directly after turning right or moving forward.

I am not sure what the difference is between calling robot after the if-else statements (in program 1) versus calling it in the then and else case in program 2. Am I correct in saying that these two programs are equivalent? Any insights are appreciated.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
ceno980
  • 2,003
  • 1
  • 19
  • 37
  • 4
    Don't you need `do turn Right; robot` in the second case (`;` standing for new line)? – Alexey Romanov Jul 26 '19 at 07:04
  • Why does there have to be a do turn Right; robot for program two? Is it needed because in order to glue multiple IO actions together? – ceno980 Jul 26 '19 at 08:12
  • Yes, for your version I am not sure why it would compile. – Alexey Romanov Jul 26 '19 at 08:38
  • @ceno980 Because the then and else parts of if/then/else can be arbitrary expressions so they use normal Haskell expression syntax. They're not automatically `do` block syntax just because the whole if/then/else happened to occur in a do block. You want to sequence two IO actions. One way to do that is to write them on separate lines of a `do` block. In ordinary expression syntax writing them on multiple lines doesn't do that; it means the same thing as `turn Right robot` on one line. If you want `do` syntax you need to start a new `do` block. – Ben Jul 26 '19 at 08:38
  • How do you know that robot is an IO action? Is it because IO actions are used in the do block? – ceno980 Jul 26 '19 at 08:41
  • @ceno980 The type signature you have for `turn` tells me it produces `IO ()`. All the statements in a `do` block must use the same monad type (which is also the monad type of the`do` block as a whole). – Ben Jul 26 '19 at 10:38

1 Answers1

7

You are correct in saying that these two programs are equivalent. More generally, if cond then (x >> action) else (y >> action) is equivalent to (if cond then x else y) >> action. This is a consequence of the fact that f (if cond then x else y) = if cond then (f x) else (f y); if you take f = (>> action) you get the equivalence for monads.

bradrn
  • 8,337
  • 2
  • 22
  • 51
  • 2
    Note that, to be pedantic, the law you mention holds for _strict_ `f`, only. E.g. `cond = undefined` and `f = \_ -> ()` break the law. That being said, `f = (>> action)` is strict in the IO monad, so everything works. – chi Jul 26 '19 at 07:47
  • Thanks for the correction @chi! Should I edit that into my post? – bradrn Jul 26 '19 at 07:53
  • Nah, I'm just nitpicking. It's fairly common to mention laws which should hold in Haskell, but actually do so only in a "moral" sense, if we neglect bottoms here and there. – chi Jul 26 '19 at 09:52