Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a variable to several sequenced functions in IO

Tags:

haskell

There are a bunch of IO computations whose result don't matter, and that rely on a same context variable. I would like an aesthetical way to pass this argument to all functions.

Say that I want to print a char several times

c = 'c'
main = do
  putChar c
  putChar c
  putChar c

but I don't want to write the parameter each time.

The following works just fine:

samearg :: (Foldable t, Monad m) => t (a -> m ()) -> a -> m ()
samearg fs ctx = foldl (\d f -> d >>= \_ -> f ctx) (return ()) fs
(>^=) = flip samearg

'c' >^= [putChar,putChar,putChar]

Now, I'm just curious whether I could have written things according to my initial idea, or whether there is some standard way to do this. I wanted to write something like 'c' >^= putChar >^= putChar >^= putChar that would reduce to this

((return 'c' >>= putChar >>= \_ -> return 'c')
>>= putChar >>= \_ -> return 'c')
>>= putChar

but this operator I wrote isn't reducing to what I expected

(>^=) :: Monad m => m b -> (b -> m a) -> m b
(>^=) ctx f = ctx >>= f >>= \_ -> ctx

return 'c' >^= putChar >^= putChar >^= putChar

which I understand, but I'm still wondering whether I could have made it work.

like image 449
nbrr Avatar asked Dec 31 '25 01:12

nbrr


2 Answers

(This makes the possibly too-optimistic assumption that all the functions have a return type of IO ().)


putChar has type Char -> IO (). There are three types here, all of which have Monoid instances:

  • (Since base-4.9) () <> () == ()
  • (Since base-4.10) If b is a monoid, then IO b is as well; m1 <> m2 == (liftA2 (<>)) m1 m2 (a new IO action, in which the results of executing the original IO actions are combined, is returned).
  • (Since base-4.9) If b is a monoid, then for f,g :: a -> b we have f <> g == \x -> f x <> g x (both functions are called on the same argument and the return values are combined.

Putting this all together, functions of type Char -> IO () form a monoid.

> :t putChar <> putChar <> putChar
putChar <> putChar <> putChar :: Char -> IO ()
> (putChar <> putChar <> putChar) 'c'
ccc>

So you can simply write

main = putChar <> putChar <> putChar $ c

or

main = mconcat (replicate 3 putChar) $ c
like image 130
chepner Avatar answered Jan 01 '26 18:01

chepner


Here's a fixed version of (>^=)

(>^=) :: Monad m => m b -> (b -> m a) -> m b
(>^=) ctx f = ctx >>= \y -> f y >> return y   -- only uses ctx once

You can also put the actions in a list and traverse it, using existing combinators:

traverse_ (\go -> go 'c') [putChar, putChar, putChar]
like image 39
Li-yao Xia Avatar answered Jan 01 '26 18:01

Li-yao Xia



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!