有没有办法合并两个OutputStreams?

时间:2020-06-11 08:47:17

标签: haskell stream

OutputStreamlooks 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]

如果使其可执行并在控制台中运行,则应该看到一些交错的红线和蓝线。

1 个答案:

答案 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类别中的单等函子的实例是否正确?

OutputStreammerge一起形成一个单面仿函数,但不是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)

您拥有的单项式类别是函数的类别,其中笛卡尔积(即元组)作为张量积,以下称为“ (->) (,)”。碰巧OutputStreammerge是在单面类别(->)及其反面之间的单面函子。

确认的唯一方法是自己遍历定义。这是您必须检查的所有内容的粗略清单,其中隐含涉及定义许多功能并证明它们满足各种规律,因此,如果您不熟悉这些概念,则每个项目都会隐藏大量内容:

  • (->)类别
  • (->)(,) monoidal类别
  • 我们可以取一个单面类别的相反,这是另一个单面类别。在这里,我们得到(<-)(,)
  • OutputStream是类别(->)及其相对的(<-)之间的 functor
  • (OutputStream _, OutputStream _)OutputStream (_, _)是(bi) functors ,其中域是产品类别(->) x (->),共域是(<-)。 (这是将函子(,)与函子OutputStream组成的两种方法,因此,如果您接受可以组成函子的话,这是免费的,但是将其拼写出来以遵循函数很重要。下面的其他要点)。
  • merge(或更严格地说,是uncurry merge)是(OutputStream _, OutputStream _)OutputStream (_, _)之间的自然变换
  • merge满足一些其他相干律(涉及(->)(,)的单项分类结构):OutputStream和{{1} }是介于merge(带有(->))和相反的(,)(带有(<-))之间的 monical函子