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.