Move through list and keep state

Viewed 330

I am trying to transform this pseudocode into Haskell:

function whatever(list)
    int somestate
    foreach list as item
        if item === 1
              state++
        else 
              state--
    endforeach
    return state

Now something like this is obviously wrong (state is 3):

test :: [Int] -> Int
test item
       | head item > 1 = 1
       | otherwise = newItem
       where newItem = tail item

What is the best way to achieve something like this in Haskell?

4 Answers

You can do this simply by using another parameter to the function:

test [] counter = counter
test (x:xs) counter
    | x == 1 = test xs (counter + 1)
    | otherwise = test xs (counter - 1)

Here we use pattern matching (x:xs) to bind the head of the list to x, and the tail of the list to xs. Once we get to the empty list, we return the final counter.

We'd have to call this function with an initial counter, like so

test [1..3] 0

However, we can do this better by not having to pass in an initial counter, by accumulating the counter as we go, like so.

test [] = 0
test (x:xs)
    | x == 1 = test xs + 1
    | otherwise = test xs - 1

This time, each time the value is equal to one, we add one to the result of the function on the empty list. Eventually we will meet the base case, which is 0, and the rest of the values will be added up giving us the final value.

However, this pattern of recursing over a list and doing something which the value is very common. We can use a fold:

test = foldl' (\acc x -> if x == 1 then acc + 1 else acc - 1) 0

This is essentially our previous recursive function.

There are ways to store persistent state in Haskell, but generally we don't need them in simple cases like this.

I think I'd probably do this:

whatever xs = length ones - length others where
    (ones, others) = partition (1==) xs

The only thing that would give me pause is some consideration for long lists; if it is important that the list not be all in memory at once, this wouldn't work well.

Another alternative:

> sum . map (bool (-1) 1 . (==1)) $ [1,2,2]
-1
> sum . map (bool (-1) 1 . (==1)) $ [1,2,2,1]
0
> sum . map (bool (-1) 1 . (==1)) $ [1,2,2,1,1,1,1,1]
4

Note that bool (-1) 1 . (==1) can also be written using basic syntax as

(\x -> if x==1 then 1 else -1)

You just tweak it and shuffle a few words around, like

foo (list) = 
    -- int somestate
    -- foreach list as item   
    -- foreach item in list
           do  item <- list
               if item == 1
                 then  return incr -- state
                 else  return decr -- state
    -- return state

incr state = state + 1
decr state = state - 1

and already get yourself a thing, with a type deduced for it! which is

foo :: (Num a, Num b, Monad m, Eq a) => m a -> m (b -> b)

We know m ~ [] so it's actually

foo :: (Num a1, Num a, Eq a) => [a] -> [b -> b]

So, what can we do with the bunch of state-updating functions, like incr/decr, in a row? A couple of things:

import Control.Monad
import Data.Monoid
import Data.Foldable

sequence . foo    :: (Num a, Num b, Eq a) => [a] -> b -> [b]

foldr (.) id . foo :: (Num a, Num b, Eq a) => [a] -> b -> b

appEndo . foldMap Endo . foo 
                   :: (Num a, Num b, Eq a) => [a] -> b -> b

foldl (.) id . foo :: (Num a, Num b, Eq a) => [a] -> b -> b

appEndo . getDual . foldMap (Dual . Endo) . foo  
                   :: (Num a, Num b, Eq a) => [a] -> b -> b

Not the first one, certainly.

It would seem that the one with foldl is the ticket here. But then again, it goes against non sunt multiplicanda praeter necessitatem. So really, it's

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = foldr g id 
    where
    g x r state | x==1 = r (incr state)     -- or even `r $! incr state`
             | otherwise = r (decr state)   --         `r $! decr state`

or simpler,

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = foldr g id 
    where
    g x r | x==1 = r . incr
       | otherwise = r . decr

or even just

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = foldr (\x -> if x==1 then (. incr) else (. decr)) id 

which actually is the one with the Dual we had all along. I think. Have fun validating this (or otherwise). :)


update: Fusing foldr (.) id . foo into one function leads to an even nicer looking

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = flip $ foldr (\x -> if x==1 then incr else decr) 

but it processes the state in the opposite order.

Related