[Haskell-beginners] Recursive update of Records with IO

Daniel Fischer daniel.is.fischer at googlemail.com
Mon Oct 10 14:50:52 CEST 2011


On Monday 10 October 2011, 13:53:17, Alia wrote:
> Hi folks,
> 
> 
> 
> (Apologies if there is duplication, I didn't see my post archived so I
> assumed I had to subscribe to be able to post)
> 
> 
> In the hope of tapping the collective wisdom, I'd like to share a
> problem which seems to be worth sharing: how to recursively update a
> single record instance with a list of IO tainted functions.
> 
> A simple pure IO-less version of what I am trying to do is probably the
> best way to explain this:
> 
> <pure_version>
> 
> module Test
> 
> where
> 
> data Person = Person {name :: String, age :: Int} deriving Show
> 
> setName :: String -> Person -> Person
> setName s p = p {name=s}
> 
> setAge :: Int -> Person -> Person
> setAge i p = p {age=i}
> 
> update :: [Person -> Person] -> Person -> Person
> update [] p  = p

> update
>  [f] p = f p

This clause is unnecessary, you can omit it.

> update (f:fs) p = update fs p'
>     where
>         p' = f p
> 
> p1 = Person {name="sue", age=12}
> p2 = update [(setName "sam"), (setAge 32)] p1
> 
> </pure_version>
> 
> This works very nicely.

Note that update is a special case of a very general pattern, a fold.
In this case a left fold, since you're combining the functions with the 
person in the order the functions appear in the list.

Prelude> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a

The type of your list elements is b = (Person -> Person),
the initial value has type a = Person.

So what's missing to use foldl is the function of type
(a -> b -> a), here (Person -> (Person -> Person) -> Person).
Now, to combine a Person with a (Person -> Person) function to yield a 
Person, there are two obvious choices,
1. const: const p f = p  -- obviously not what we want here
2. application: app p f = f p -- i.e. app = flip ($).

We can define a pretty operator for that,

infixl 0 |>

(|>) :: a -> (a -> b) -> b
x |> f = f x

and get

update fs p = foldl (|>) p fs

Note: foldl is almost never what you want, instead you want foldl' from 
Data.List, see e.g.
http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27
But it doesn't matter here, as Person is defined, foldl' won't give you 
enough strictness if the list of functions is long; but the list of person-
updaters will surely be short in general.

For completeness, the type of the right-fold, foldr:

Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b

> 
> Now if the setter functions involve some IO, I believe the type
> signatures should probably look like this:
> 
> setName :: String -> Person -> IO Person
> setAge :: Int -> Person -> IO Person
> update :: [Person -> IO Person] -> Person -> IO Person
> 
> and the setter functions should look like this for example:
> 
> setName :: String -> Person -> IO Person
> setName s p = do
>     putStrLn "setting name"
>     return p {name=s}
> 
> setAge :: Int -> Person -> IO Person
> setAge i p = do
>     putStrLn "setting age"
>     return p {age=i}
> 
> but I'm stuck on how
>  the update function would look.. Any help would be much appreciated.

We can closely follow the pure case,

update [] p = return p  -- nothing else we could do
update (f:fs) p = do
  p' <- f p
  update fs p'

The main difference is that instead of binding the updated person with a 
where or let, as in the pure case, we bind it in the IO-monad, since that's 
f's result type.

Instead of coding the recursion ourselves, we can also use a standard 
combinator here,

Prelude Control.Monad> :i foldM
foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
        -- Defined in Control.Monad

The Monad here is IO, a again is Person, and b is now
(Person -> IO Person).

For the combinator
Person -> (Person -> IO Person) -> IO Person
there are again two obvious possibilities,
1. return -- analogous to const
2. application, (|>)

and the IO-version becomes

import Control.Monad

update fs p = foldM (|>) p fs




More information about the Beginners mailing list