Question

Consider the following Haskell code:

import Control.Monad.State

test :: Int -> [(Int, Int)]
test = runStateT $ do
    a <- lift [1..10]
    modify (+a)
    return a

main = print . test $ 10

This produces the following output:

[(1,11),(2,12),(3,13),(4,14),(5,15),(6,16),(7,17),(8,18),(9,19),(10,20)]

However I would like to produce the following output instead:

[(1,11),(2,13),(3,16),(4,20),(5,25),(6,31),(7,38),(8,46),(9,55),(10,65)]

This is easy to do in an impure language like JavaScript:

function test(state) {
    var result = [];

    for (var a = 1; a <= 10; a++) {
        result.push([a, state += a]);
    }

    return result;
}

How do you do the same thing in Haskell?

Was it helpful?

Solution

The Haskell types and the logic of your JavaScript code doesn't match: the JS code has two values in the state (the Int, and the returned list). In contrast, StateT Int [] a doesn't really have a list in the state; rather, it runs stateful actions multiple times (with the initial state unchanged for each run) and collects all the results in a list.

In other words, the JS code has type State (Int, [(Int, Int)]) [(Int, Int)]. But this is too literal a translation and we can write more elegant Haskell code.

Sticking to the State monad, we can return a list with mapM or forM:

test2 :: Int -> [(Int, Int)]
test2 = evalState $ 
    forM [1..10] $ \a -> do
        s <- get <* modify (+a)
        return (a, s) 

Some lens magic can make it more similar to the JS code:

{-# LANGUAGE TupleSections #-}

import Control.Lens

test3 :: Int -> [(Int, Int)]
test3 = evalState $ 
    forM [1..10] $ \a -> (a,) <$> (id <+= a)

However, we can do away with State altogether, and it's the best approach here, I think:

import Control.Monad (ap)

test4 :: Int -> [(Int, Int)]
test4 n = ap zip (tail . scanl (+) n) [1..10]
-- or without ap : zip [1..10] (drop 1 $ scanl (+) n [1..10])

OTHER TIPS

Your series is quadratic and can be generated more simply:

series = fmap (\x -> (x, quot (x*x + x) 2 + 10)) [1..]

If you want a recurrence relation you might write it as this:

series :: [(Int,Int)]
series = let
  series' x y = (x, x + y) : series' (x + 1) (x + y)
  in series' 1 10

There is no compelling reason that I see to use monads.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top