Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good way of creating loops

Tags:

haskell

Haskell does not have loops like many other languages. I understand the reasoning behind it and some of the different approaches used to solve problems without them. However, when a loop structure is necessary, I am not sure if the way I'm creating the loop is correct/good.

For example (trivial function):

dumdum = do
         putStrLn "Enter something"
         num <- getLine
         putStrLn $ "You entered: " ++ num
         dumdum

This works fine, but is there a potential problem in the code?

A different example:

a = do 
    putStrLn "1"
    putStrLn "2"
    a

If implemented in an imperative language like Python, it would look like:

def a():
     print ("1")
     print ("2")
     a()

This eventually causes a maximum recursion depth error. This does not seem to be the case in Haskell, but I'm not sure if it might cause potential problems.

I know there are other options for creating loops such as Control.Monad.LoopWhile and Control.Monad.forever -- should I be using those instead? (I am still very new to Haskell and do not understand monads yet.)

like image 408
XrXr Avatar asked Dec 07 '22 02:12

XrXr


1 Answers

For general iteration, having a recursive function call itself is definitely the way to go. If your calls are in tail position, they don't use any extra stack space and behave more like goto1. For example, here is a function to sum the first n integers using constant stack space2:

sum :: Int -> Int
sum n = sum' 0 n

sum' !s 0 = s
sum' !s n = sum' (s+n) (n-1)

It is roughly equivalent to the following pseudocode:

function sum(N)

    var s, n = 0, N
    loop: 
       if n == 0 then
           return s
       else
           s,n = (s+n, n-1)
           goto loop

Notice how in the Haskell version we used function parameters for the sum accumulator instead of a mutable variable. This is very common pattern for tail-recursive code.

So far, general recursion with tail-call-optimization should give you all the looping power of gotos. The only problem is that manual recursion (kind of like gotos, but a little better) is relatively unstructured and we often need to carefully read code that uses it to see what is going on. Just like how imperative languages have looping mechanisms (for, while, etc) to describe most common iteration patterns, in Haskell we can use higher order functions to do a similar job. For example, many of the list processing functions like map or foldl'3 are analogous to straightforward for-loops in pure code and when dealing with monadic code there are functions in Control.Monad or in the monad-loops package that you can use. In the end, its a matter of style but I would err towards using the higher order looping functions.


1 You might want to check out "Lambda the ultimate GOTO", a classical article about how tail recursion can be as efficient as traditional iteration. Additionally, since Haskell is a lazy languages, there are also some situations where recursion at non-tail positions can still run in O(1) space (search for "Tail recursion modulo cons")

2 Those exclamation marks are there to make the accumulator parameter be eagerly evaluated, so the addition happens at the same time as the recursive call (Haskell is lazy by default). You can omit the "!"s if you want but then you run the risk of running into a space leak.

3 Always use foldl' instead of foldl, due to the previously mentioned space leak issue.

like image 118
hugomg Avatar answered Dec 09 '22 15:12

hugomg