Cameron
Cameron

Reputation: 113

How can I execute multiple statements in a single function?

I am learning Haskell and for an assignment I have to print a Snakes and Ladders game. Starting out, I am trying to print the board and this is what I've done.

import Data.List
aRow :: Int -> String
aRow n = "+" ++ take (4*n) (intercalate "" (repeat "---+")) ++ "\n|" ++      take (4*n) (intercalate "" (repeat "   |")) ++ "\n"

board :: Int -> Int -> IO()
board 1 y = putStrLn (aRow y)

I would like another instance of board such that it takes arguments x and y

board x y = putStrLn (aRow y)
            board (x-1) y

I know that I can't just call multiple statements like this, but can anyone provide some insight about how I can go along with this? I want to call aRow with argument 'y' and do that 'x' times.

Thanks.

Also: when I call board 1 y I get this as output: board 1 5
+---+---+---+---+---+
|   |  |  |   |  |

Upvotes: 2

Views: 2418

Answers (3)

chepner
chepner

Reputation: 531165

You just need to sequence the two monadic functions:

board x y = putStrLn (aRow y) >> board (x - 1) y

or with do notation

board x y = do
    putStrLn (aRow y)
    board (x - 1) y

Note that x == 0 makes a more natural base case:

board 0 y = return ()
board x y = do
   putStrLn (aRow y)
   board (x - 1) (aRow y)

See Boomerang's answer for a more idiomatic way of writing the function, though.

Upvotes: 2

basile-henry
basile-henry

Reputation: 1365

I think the cleanest way is to create the board without doing any IO and then at the end only to print it out using IO.

You can use concat and replicate to achieve this:

board :: Int -> Int -> String
board x y = concat (replicate y (aRow x))

You are probably missing a line at the bottom but I'll let you figure this out!

By the way, take (4*n) (intercalate "" (repeat "---+")) is the same as concat (replicate n "---+") so you could write aRow as:

aRow :: Int -> String
aRow n = '+' : concat (replicate n "---+")
      ++ "\n|" ++ concat (replicate n "   |")
      ++ "\n"

Edit: I would use unlines :: [String] -> String to concatenate several Strings on multiple lines:

aRow :: Int -> String
aRow n = unlines
  [ '+' : concat (replicate n "---+")
  , '|' : concat (replicate n "   |")
  ]

Upvotes: 7

leftaroundabout
leftaroundabout

Reputation: 120711

So you want to execute an IO (), and then yet another IO () action. Together they should be an IO () as well. So you're looking for a combinator with signature IO () -> IO () -> IO (). You can ask Hoogle about this... oh dear, that gives quite a lot of irrelevant results. But also the right one, namely

(>>) :: Monad m => m a -> m b -> m b

Your IO () -> IO () -> IO () is a special case of this signature, obtained by setting m ~ IO and a ~ b ~ (). So, you can write

board x y = putStrLn (aRow y)
         >> board (x-1) y

Because these monadic sequencing operators are quite often used in Haskell, it has special syntactic-sugar syntax for them, namely

board x y = do
    putStrLn (aRow y)
    board (x-1) y

Ok so this works, but it's not really idiomatic. Manual recursion “loops” with a “counter variable” x are awkward and rather error-prone (you need to get the initial, termination and stepping conditions right). Really, all you're doing there is execute the same action x times in a row. So really you're interested in Int -> IO () -> IO (). Again ask Hoogle; this time the right result comes up a bit earlier...

replicateM_ :: Applicative m => Int -> m a -> m ()

so

board :: Int -> Int -> IO ()
board x y = replicateM_ x $ putStrLn (aRow y)

Even better, as Boomerang remarks, is to avoid IO looping altogether.

Upvotes: 3

Related Questions