OutputStream
是looks like a contravariant functor的抽象-一种思考的方式是延续。特别是,它与容器InputStream
不同,它不是容器,它不能为您提供任何实际值。
It has been shown previously how to split an InputStream
.我想换一种方式—合并OutputStream
。也就是说,我需要这样的功能:
contrafork :: OutputStream a → OutputStream b → IO (OutputStream (a, b))
contrafork = …
想到它的一种方法是从instance Applicative OutputStream
开始。
contrafork :: OutputStream a → OutputStream b → IO (OutputStream (a, b), Async ( ))
contrafork ω₁ ω₂ = do
buffer ← newEmptyMVar
ω ← ω₁ & contramapM (\ (x, y) → (putMVar buffer . Just $ y) >> return x) >>= atEndOfOutput (putMVar buffer Nothing)
α ← makeInputStream (takeMVar buffer)
token ← async $ connect α ω₂
return (ω, token)
connect
is a loop under the hood-这是执行操作的地方。我必须使其异步,以避免出现«无限期阻塞线程»的情况,然后我必须在外部传递令牌,消费者应等待以确保两个流完全执行。在错误的位置等待仍会导致线程锁定。总而言之,此解决方案尚待改进。
这是我用来查看我的contrafork
是否有效的跑步者:
#!/usr/bin/env stack
{- stack --resolver=lts-14 script
--package io-streams
--package bytestring
--package ansi-terminal
--package async
--ghc-options -Wall
-}
{-# language UnicodeSyntax #-}
{-# language OverloadedStrings #-}
{-# language BlockArguments #-}
import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception
import Data.Function
import System.Console.ANSI
import System.IO.Streams hiding (map)
import qualified System.IO.Streams as Streams
import qualified System.IO.Streams.ByteString as ByteString
main :: IO ( )
main = do
α ← "1\n2\n3\n" & fromByteString >>= ByteString.lines
consoleWriteLock ← newMVar ( )
[ω₁, ω₂] ← traverse (makeOutputStream . logMaybeLineWithColour consoleWriteLock) [Red, Blue]
(ω², token) ← contrafork ω₁ ω₂
α² ← Streams.map (\ x → (x, x)) α
connect α² ω²
wait token
where
logMaybeLineWithColour lock colour = maybe (return ( )) \ line → do
( ) ← takeMVar lock
withSGRs [SetColor Foreground Vivid colour] $ print $ "Output line: " <> line
putMVar lock ( )
contrafork :: OutputStream a → OutputStream b → IO (OutputStream (a, b), Async ( ))
contrafork ω₁ ω₂ = do
buffer ← newEmptyMVar
ω ← ω₁ & contramapM (\ (x, y) → (putMVar buffer . Just $ y) >> return x) >>= atEndOfOutput (putMVar buffer Nothing)
α ← makeInputStream (takeMVar buffer)
token ← async $ connect α ω₂
return (ω, token)
withSGRs :: [SGR] → IO a → IO a
withSGRs sgrs action = bracket open close \ _ → action
where
open = setSGR sgrs
close _ = setSGR [Reset]
如果使其可执行并在控制台中运行,则应该看到一些交错的红线和蓝线。
答案 0 :(得分:2)
OutputStream a
被定义为Maybe a -> IO ()
,因此可以如下合并两个流:
-- Let the types guide you.
merge_ :: (Maybe a -> IO ()) -> (Maybe b -> IO ()) -> (Maybe (a, b) -> IO ())
merge_ eatA eatB Nothing = eatA Nothing >> eatB Nothing
merge_ eatA eatB (Just (a, b)) = eatA (Just a) >> eatB (Just b)
-- Use `writeTo` and `makeOutputStream` to convert between `OutputStream a` and its underlying representation `Maybe a -> IO ()`.
merge :: OutputStream a -> OutputStream b -> OutputStream (a, b)
merge streamA streamB = makeOutputStream (merge_ (writeTo streamA) (writeTo streamB))
本附录假定您对类别理论有所了解。
评论中还有另一个问题:
将其称为IO的Kleisli类别中的单等函子的实例是否正确?
OutputStream
和merge
一起形成一个单面仿函数,但不是Kleisli IO
中的。
在单曲面类别之间定义了一个单曲面仿函数,因此我们首先必须拼出这些类别。 monoidal类别是具有张量积的类别。在本文的其余部分,我们将单等分类称为“ _与_”;实际上,如果可以从上下文中推断出张量积(至少说过一次),则通常会将其保留为隐式。
在这里,最简单的候选类别是Kleisli IO
和(,)
,但这不是一个单一类别:(,)
甚至都不是双音符(当Kleisli IO
是相关类别),这将是张量积的必要条件。当尝试满足bifunctor的定义时,会遇到以下问题:没有具有满足合成法则的签名的功能:
bimap :: (a -> IO c) -> (b -> IO d) -> ((a, b) -> IO (c, d))
-- Identity law (OK):
-- bimap pure pure = pure
-- Composition law (BROKEN):
-- bimap f1 g1 >=> bimap f2 g2 = bimap (f1 >=> f2) (g1 >=> g2)
您拥有的单项式类别是函数的类别,其中笛卡尔积(即元组)作为张量积,以下称为“ (->)
(,)
”。碰巧OutputStream
与merge
是在单面类别(->)
及其反面之间的单面函子。
确认的唯一方法是自己遍历定义。这是您必须检查的所有内容的粗略清单,其中隐含涉及定义许多功能并证明它们满足各种规律,因此,如果您不熟悉这些概念,则每个项目都会隐藏大量内容:
(->)
是类别。(->)
与(,)
是 monoidal类别。(<-)
和(,)
。OutputStream
是类别(->)
及其相对的(<-)
之间的 functor 。(OutputStream _, OutputStream _)
和OutputStream (_, _)
是(bi) functors ,其中域是产品类别(->) x (->)
,共域是(<-)
。 (这是将函子(,)
与函子OutputStream
组成的两种方法,因此,如果您接受可以组成函子的话,这是免费的,但是将其拼写出来以遵循函数很重要。下面的其他要点)。merge
(或更严格地说,是uncurry merge
)是(OutputStream _, OutputStream _)
和OutputStream (_, _)
之间的自然变换。merge
满足一些其他相干律(涉及(->)
和(,)
的单项分类结构):OutputStream
和{{1} }是介于merge
(带有(->)
)和相反的(,)
(带有(<-)
)之间的 monical函子。