Explicit looping is commonplace in imperative programming languages. For example, this is how I might write a loop in Python.
= 0
wrongAttempts
while True:
print("Enter password")
= input()
s if s != "letmein":
+= 1
wrongAttempts print("You have been incorrect", wrongAttempts, "times")
else:
break
print("You are into the system!")
We can always replace imperative looping with recursion.
= do
main let myLoop wrongAttempts = do
putStrLn "Enter password"
<- getLine
s /= "letmein") $ do
when (s let wrongAttempts' = wrongAttempts + 1
putStrLn
"You have been incorrect "
( ++ show wrongAttempts'
++ " times"
)
myLoop wrongAttempts'
0
myLoop putStrLn "You are into the system!"
There are a couple of small differences between the Python version and the Haskell version, and two bigger ones. The small ones are that in the Haskell version we explicitly continue by naming myLoop
whereas the Python version explicitly break
s instead, and naturally the Haskell version explicitly passes the state for the next iteration.
The two bigger differences make the Haskell version clumsier. Firstly we have to name myLoop
and call it explicitly. Secondly the initial state (0
) is passed in far from the definition of myLoop
.
How can we fix this? Let’s define a genereric looping combinator, loop
. To make my loop less clumsy I want to avoid naming myLoop
and I want to pass the initial state in to loop
directly. This suggests I want to write something like
0 (\continue wrongAttempts -> do
loop ...
continue wrongAttempts')
where the loop name continue
is now bound in a lambda.
Can we implement this? Well, loop
will have to look like
= body ... state loop state body
but what can I pass as the second argument to body
? It needs to be something that takes the updated state and continues with the loop. But that’s just flip loop body
! So I can define
= body (flip loop body) state loop state body
and then I can write main2
which doesn’t have the original clumsinesses.
= do
main2
loop0
-> do
(\continue wrongAttempts putStrLn "Enter password"
<- getLine
s /= "letmein") $ do
when (s let wrongAttempts' = wrongAttempts + 1
putStrLn
"You have been incorrect "
( ++ show wrongAttempts'
++ " times"
)
continue wrongAttempts'
)putStrLn "You are into the system!"
Note that flip loop body state = body (flip loop body) state
so flip loop body = body (flip loop body)
. If we write fix f = f (fix f)
then loop = flip fix
. N.B. fix
is normally written as fix f = let x = f x in x
for efficiency.