I am having a hard time understanding this tutorial: https://acm.wustl.edu/functional/state-monad.php
I am creating my own function that reverses a list and returns a State
with the lowest element and the reverse of the list. I am very new to Haskell as well. Here is my code:
myFunct :: Ord a => [a] -> State a [a]
myFunct t = do
let s = reverse t
let a = minimum t
return s a
I can't find an other material on this either. This is the error I am getting.
Couldn't match type ‘[a]’
with ‘StateT a Data.Functor.Identity.Identity [a]’
Expected type: a -> State a [a]
Actual type: a -> [a]
• The function ‘return’ is applied to two arguments,
its type is ‘a0 -> m0 a0’,
it is specialized to ‘[a] -> a -> [a]’
In a stmt of a 'do' block: return s a
In the expression:
do let s = reverse t
let a = minimum t
return s a
You're in luck: State
is the easiest monad to understand.
Please do not get discouraged by the fact that your function does not need State
at all, insofar as you use reverse
and minimum
from the standard library.
myFunct' :: Ord a => [a] -> ([a], a)
myFunct' xs = (reverse xs, minimum xs)
(It would run like this:)
λ myFunct' [1,2,3]
([3,2,1],1)
Notice though that, in order for you to apply both reverse
and minimum
to a list, you will need to traverse it two times. This is when State
may get handy: using it, you can only traverse the list once, thus, hopefully, gaining some speedup. Read on to find out how.
So, State
is a function of a special kind: the thing you give it (also called "state") is kept in a magic box where you can observe it, or replace it with another thing of the same type at any time. If you have experience with imperative languages, you may find it easy to think of State
as an imperative procedure and "state" as a local variable. Let us review the tools that you may use to construct and execute a State
:
You may observe the thing in the box with the (inappropriately named) function get
. Notice that this does not change the state in any way − what you obtain is merely an immutable copy of its current value; the thing stays in the box.
You would usually associate your observation with a name, then use it as an ordinary value − for example, pass to a pure function:
stateExample1 :: State Integer Integer
stateExample1 = do
x <- get -- This is where we observe state and associate it with the name "x".
return $ x * 2 -- (* 2) is an example of a pure function.
λ runState stateExample1 10
(20,10) -- The first is the return value, the second is the (unchanged) state.
You may replace the thing in the box with another suitably typed thing; use the function put
:
stateExample2 :: State Integer Integer
stateExample2 = do
x <- get
put $ x * 2 -- You may think of it as though it were "x = x * 2"
-- in an imperative language.
return x
λ runState stateExample2 10
(10,20) -- Now we have changed the state, and return its initial value for reference.
Notice that, though we changed the state, our observation of it (that we named "x") still has the same value.
You may run the State
function, giving it an argument (we'd call it "initial state"):
y = runState stateExample1 10
It is the same as:
y = stateExample1(10);
− in an imperative language with C-like syntax, except that you obtain both the return value and the final state.
Armed with this knowledge, we can now rewrite your proposed myFunct
like this:
myFunct :: Ord a => [a] -> State (Maybe a) [a]
myFunct [ ] = return [ ]
myFunct t = do
let s = reverse t
let a = minimum t
put (Just a)
return s
λ runState (myFunct [1,2,3]) (Just (-100))
([3,2,1],Just 1)
λ runState (myFunct []) (Just (-100))
([],Just (-100))
If we regard State
as an imperative procedure, then the reversed list is what it returns, while the minimum of the list is what its final state would be. As the list may be empty, we have provisioned an optional default value for the minimum. This makes the function total, which is considered good Haskell style:
λ myFunct' []
([],*** Exception: Prelude.minimum: empty list
λ runState (myFunct []) Nothing
([],Nothing)
Now, let us reap the benefit of State
by writing a function that returns both the minimum and the reverse of a list in one pass:
reverseAndMinimum :: Ord a => [a] -> ([a], Maybe a)
reverseAndMinimum xs = runState (reverseAndMinimum' xs [ ]) Nothing
reverseAndMinimum' :: Ord a => [a] -> [a] -> State (Maybe a) [a]
reverseAndMinimum' [ ] res = return res
reverseAndMinimum' (x:xs) res = do
smallestSoFar <- get
case smallestSoFar of
Nothing -> put $ Just x
Just y -> when (x < y) (put $ Just x)
reverseAndMinimum' xs (x: res)
First off, this is an iterative algorithm that thus needs a starting value for the minimum. We hide this fact in reverseAndMinimum'
, supplying Nothing
for the starting value.
The logic of the reverse part I borrowed from the modern Prelude.reverse
. We simply move elements from the first argument xs
to the second argument res
, until xs
is empty.
This is the part that finds the smaller of the current x
and the value stored in the state box. I hope you'll find it readable.
case smallestSoFar of
Nothing -> put $ Just x
Just y -> when (x < y) (put $ Just x)
This is the part that does the recursion:
reverseAndMinimum' xs (x: res)
It applies reverseAndMinimum'
again, but to a strictly smaller list xs
; the monadic wiring automagically transfers the box with the current minimum down the line.
Let us trace the execution of a call to reverseAndMinimum'
. Suppose we say:
runState (reverseAndMinimum' [1,2,3] [ ]) Nothing
What will happen?
1
and Nothing
is 1
. So, the Nothing
in the box will be replaced by Just 1
.The State
will be called again, as though we called it with a code like this:
runState (reverseAndMinimum' [2,3] [1]) (Just 1)
And so on, until the parameter becomes an empty list, by which time the box will surely contain the smallest number.
This version actually performs faster than myFunct'
by about 22%, and uses somewhat less memory as well. (Though, as you may check in edit history, it took some effort to get to it.)
That's it. I hope it helps!
Special thanks to Li-Yao Xia who helped me devise the code for reverseAndMinimum
that actually beats myFunct'
.