Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>There are two ways to do this without using concurrency, both with caveats.</p> <p>The first way is that if <code>pipe1</code> and <code>pipe2</code> are just simple <code>Consumer</code>s that loop forever like:</p> <pre><code>p1 = for cat f -- i.e. p1 = forever $ await &gt;&gt;= f p2 = for cat g -- i.e. p2 = forever $ await &gt;&gt;= g </code></pre> <p>... then the easy way to solve this is to just write:</p> <pre><code>for P.stdinLn $ \str -&gt; do f str g str </code></pre> <p>For example, if <code>p1</code> is just <code>print</code>ing every value:</p> <pre><code>p1 = for cat (lift . print) </code></pre> <p>... and <code>p2</code> is just writing that value to a handle:</p> <pre><code>p2 = for cat (lift . hPutStrLn h) </code></pre> <p>... then you would combine them like so:</p> <pre><code>for P.stdinLn $ \str -&gt; do lift $ print str lift $ hPutStrLn h str </code></pre> <p>However, this simplification only works for <code>Consumer</code>s that trivially loop. There's another solution that is more general, which is to define an <code>ArrowChoice</code> instance for pipes. I believe that pull-based <code>Pipe</code>s do not permit a correct law-abiding instance, but push-based <code>Pipe</code>s do:</p> <pre><code>newtype Edge m r a b = Edge { unEdge :: a -&gt; Pipe a b m r } instance (Monad m) =&gt; Category (Edge m r) where id = Edge push (Edge p2) . (Edge p1) = Edge (p1 &gt;~&gt; p2) instance (Monad m) =&gt; Arrow (Edge m r) where arr f = Edge (push /&gt;/ respond . f) first (Edge p) = Edge $ \(b, d) -&gt; evalStateP d $ (up \&gt;\ unsafeHoist lift . p /&gt;/ dn) b where up () = do (b, d) &lt;- request () lift $ put d return b dn c = do d &lt;- lift get respond (c, d) instance (Monad m) =&gt; ArrowChoice (Edge m r) where left (Edge k) = Edge (bef &gt;=&gt; (up \&gt;\ (k /&gt;/ dn))) where bef x = case x of Left b -&gt; return b Right d -&gt; do _ &lt;- respond (Right d) x2 &lt;- request () bef x2 up () = do x &lt;- request () bef x dn c = respond (Left c) </code></pre> <p>This requires a newtype so that the type parameters are in the order that <code>ArrowChoice</code> expects.</p> <p>If you're unfamiliar with the term push-based <code>Pipe</code>, it's basically a <code>Pipe</code> that begins from the most upstream pipe instead of the most downstream pipe, and they all have the following shape:</p> <pre><code>a -&gt; Pipe a b m r </code></pre> <p>Think of it as a <code>Pipe</code> that cannot "go" until it receives at least one value from upstream.</p> <p>These push-based <code>Pipe</code>s are the "dual" to conventional pull-based <code>Pipe</code>s, complete with their own composition operator and identity:</p> <pre><code>(&gt;~&gt;) :: (Monad m) =&gt; (a -&gt; Pipe a b m r) -&gt; (b -&gt; Pipe b c m r) -&gt; (a -&gt; Pipe a c m r) push :: (Monad m) -&gt; a -&gt; Pipe a a m r </code></pre> <p>... but the unidirectional <code>Pipes</code> API does not export this by default. You can only get these operators from <code>Pipes.Core</code> (and you may want to study that module more closely to build an intuition for how they work). That module shows that both push-based <code>Pipe</code>s and pull-based <code>Pipe</code>s are both special cases of more general bidirectional versions, and understanding the bidirectional case is how you learn why they are duals of each other.</p> <p>Once you have an <code>Arrow</code> instance for push-based pipes, you can write something like:</p> <pre><code>p &gt;&gt;&gt; bifurcate &gt;&gt;&gt; (p1 +++ p2) where bifurcate = Edge $ pull ~&gt; \a -&gt; do yield (Left a) -- First give `p1` the value yield (Right a) -- Then give `p2` the value </code></pre> <p>Then you would use <code>runEdge</code> to convert that to a pull-based pipe when you are done.</p> <p>This approach has one major draw-back, which is that you can't automatically upgrade a pull-based pipe to a push-based pipe (but usually it's not hard to figure out how to do it manually). For example, to upgrade <code>Pipes.Prelude.map</code> to be a push-based <code>Pipe</code>, you would write:</p> <pre><code>mapPush :: (Monad m) =&gt; (a -&gt; b) -&gt; (a -&gt; Pipe a b m r) mapPush f a = do yield (f a) Pipes.Prelude.map f </code></pre> <p>Then that has the right type to be wrapped up in the <code>Arrow</code>:</p> <pre><code>mapEdge :: (Monad m) =&gt; (a -&gt; b) -&gt; Edge m r a b mapEdge f = Edge (mapPush f) </code></pre> <p>Of course, an even simpler way would be just to write it from scratch:</p> <pre><code>mapEdge f = Edge $ push ~&gt; yield . f </code></pre> <p>Use whichever approach suits you best.</p> <p>In fact, I came up with the <code>Arrow</code> and <code>ArrowChoice</code> instances precisely because I was trying to answer the exact same question as you: how do you solve these kinds of problems without using concurrency? I wrote up a long answer about this more general subject in another Stack Overflow answer <a href="https://stackoverflow.com/questions/19201901/in-functional-reactive-programming-how-do-you-share-state-between-two-parts-of/19204253#19204253">here</a>, where I describe how you can use these <code>Arrow</code> and <code>ArrowChoice</code> instances to distill concurrent systems into equivalent pure ones.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload