Recursive update of Records with IO

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:

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:
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
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

<snip awesome explanation> Dear Daniel, I very much appreciate your quick and excellent reply, which is exactly what the doctor ordered. The revised update works like a charm and I will be henceforth spending more time in Control.Monad (-: Many thanks indeed! Alia

For the sake of good order, I am posting the different solutions as per Daniel Fischer's suggestions
in a tested module:

Daniel,
Excellent explanation. Thanks a lot.
Mohan.
Sent from my iPad
On Oct 10, 2011, at 20:50, Daniel Fischer
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:
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
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
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
participants (3)
-
Alia
-
Daniel Fischer
-
Gnani