Reputation: 113
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
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
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 String
s on multiple lines:
aRow :: Int -> String
aRow n = unlines
[ '+' : concat (replicate n "---+")
, '|' : concat (replicate n " |")
]
Upvotes: 7
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