Work with the state monad in Haskell

I gradually study Haskell and slowly (slowly) work on understanding the state monad, trying to write a function that repeats the state calculation until the state meets some logic test and collects the return values ​​in the list for the General result. Finally, I managed to do this:

collectUntil :: (s -> Bool) -> State sa -> State s [a] collectUntil fs = do s0 <- get let (a,s') = runState s s0 put s' if (f s') then return [a] else liftM (a:) $ collectUntil fs 

so that

 simpleState = state (\x -> (x,x+1)) *Main> evalState (collectUntil (>10) simpleState) 0 [0,1,2,3,4,5,6,7,8,9,10] 

Is this a reasonable function for this task, or is there a more idiomatic way?

+6
source share
3 answers

You make the same mistakes as I did when I first started writing monadic code, making it too complicated using liftM and using >>= (equivalently using the <- notation).

Ideally, you do not need to mention runState or evalState inside the state monad in general. The required functionality is as follows:

  • Read current status
  • If it satisfies the predicate f , return
  • If not, then run the calculation of s and add its result to the output

You can do it right this way:

 collectUntil f comp = do s <- get -- Get the current state if fs then return [] -- If it satisfies predicate, return else do -- Otherwise... x <- comp -- Perform the computation s xs <- collectUntil f comp -- Perform the rest of the computation return (x:xs) -- Collect the results and return them 

Please note that you can insert statements if they are part of the same monad! This is very useful - it allows you to embed in one do block if both branches of the if statement lead to something of the same monadic type.

The deduced type for this function:

 collectUntil :: MonadState tm => (t -> Bool) -> ma -> m [a] 

If you want, you can specialize in type State s , although you do not need to:

 collectUntil :: (s -> Bool) -> State sa -> State s [a] 

It might even be preferable to maintain a more general state if you want to use another monad later.

What is intuition?

Whenever s is a stateful calculation and you are inside the state monad, you can do

 x <- s 

and x will now have the result of the calculation (as if you had called evalState and sent it to its original state). If you need to check the status, you can do

 s' <- get 

and s' will be the value of the current state.

+9
source

Most monads come with a few primitive "run" operations, such as runState , execState , etc. If you often call runState inside the state monad, it means that you are not actually using the functionality of the monad. You wrote

 s0 <- get -- Read state let (a,s') = runState s s0 -- Pass state to 's', get new state put s' -- Save new state 

You do not need to explicitly pass the state. This is what the state monad does! You can just write

 a <- s 

Otherwise, the function looks reasonable. Since a is part of the result in both if branches, I would suggest factoring for clarity.

 collectUntil fs = step where step = do a <- s liftM (a:) continue continue = do s' <- get if fs' then return [] else step 
+4
source

For such a simple task, I would not use the State monad. The rest have already figured out how you should actually write a monadic version, but I would like to add my personal (simpler) solution, since you are asking for the most idiomatic way to write this.

 collectWhile, collectUntil :: (a -> a) -> (a -> Bool) -> a -> [a] collectWhile f cond z = takeWhile cond $ iterate fz collectUntil f cond z = collectWhile f (not . cond) z 

Alternatively, only the next line is enough if you want only collectUntil

 collectUntil f cond z = takeWhile (not.cond) $ iterate fz 

Here's takeWhile and iterate from Prelude . For completeness, since this is the core of the implementation, the following (very simple) code to iterate:

 iterate fx = x : iterate f (fx) 

warning : this may not have been clear enough from my answer, but this solution is not exactly the same as I am fusing state and result together, working outside State . Of course, you can do something very similar using f :: (s, a) -> (s, a) , and then project using map fst or map snd to get a list of intermediate states or results, respectively. For ease of notation, it might be easier to use a solution with State at this point.

+2
source

Source: https://habr.com/ru/post/919270/


All Articles