2
\$\begingroup\$

conduit offers the following exercise:

Write a conduit that consumes a stream of Ints. It takes the first Int from the stream, and then multiplies all subsequent Ints by that number and sends them back downstream. You should use the mapC function for this.

Here's what I came up with:

multiplier :: Monad m => ConduitM Int Int m ()
multiplier = do
  mx <- await
  case mx of
    Nothing -> return ()
    Just x  -> mapC (* x)

After reloading the REPL, I ran it:

λ: >runConduitPure $ yieldMany [1..5] .| multiplier .| sinkList
[2,3,4,5]
λ: >runConduitPure $ yieldMany [3..5] .| multiplier .| sinkList
[12,15]
λ: >runConduitPure $ yieldMany [] .| multiplier .| sinkList
[]
\$\endgroup\$
  • \$\begingroup\$ I've never seen mx as name for having your desired x wrapped in a Maybe - that is genius! Definitely need to do this myself from now on. \$\endgroup\$ – Phil Kiener Nov 25 '17 at 9:27
  • \$\begingroup\$ I took it straight from the README! Also, what's so genius about it? I'm not disputing it. I just don't get what mx means! \$\endgroup\$ – Kevin Meredith Nov 25 '17 at 11:17
  • 2
    \$\begingroup\$ Well, your desired variable is called x. But what await returns is a Maybe Int, so you call the "maybe it's my x, maybe it's not" mx - maybe x. \$\endgroup\$ – Phil Kiener Nov 25 '17 at 11:19
  • 1
    \$\begingroup\$ multiplier = await >>= traverse_ (mapC . (*)) \$\endgroup\$ – Gurkenglas Nov 25 '17 at 23:06
2
\$\begingroup\$

It sufficiently solves the exercise, is highly readable and easily maintainable. As Phil said, the "m" prefix for Maybe's is a nice pattern, even if it originates from Conduits README. It's something you can keep for future projects.

There's only a small nitpick: while the exercise tells you to multiply a conduit of integers, it's not necessary for the upstream or downstream values to be integers. Anything that understands (*) should work, so we can generalize it's type:

multiplier :: (Num a, Monad m) => ConduitM a a m ()

Also, we should test a single element list.


Gurkenglas' variant will work, as usual, but it depends on whether you like the pointfree style:

multiplier = await >>= traverse_ (mapC . (*))
-- vs
multiplier = await >>= traverse_ (\x -> mapC (*x))
-- vs
multiplier = do
  mx <- await
  for_ mx (\x -> mapC (*x))
-- vs
multiplier = do
  mx <- await
  case mx of
    Just x  -> mapC (*x)
    Nothing -> return ()

As they are all equivalent, I suggest you to use the one you like the most, and which you will understand even three months later. Your original one is the easiest to read.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.