这种类型的一般结构是什么?

Nik*_* B. 16 haskell types higher-kinded-types

虽然早先破解了一些东西,但我创建了以下代码:

newtype Callback a = Callback { unCallback :: a -> IO (Callback a) }

liftCallback :: (a -> IO ()) -> Callback a
liftCallback f = let cb = Callback $ \x -> (f x >> return cb) in cb

runCallback :: Callback a -> IO (a -> IO ())
runCallback cb =
    do ref <- newIORef cb
       return $ \x -> readIORef ref >>= ($ x) . unCallback >>= writeIORef ref
Run Code Online (Sandbox Code Playgroud)

Callback a表示处理某些数据并返回应该用于下一个通知的新回调的函数.一个回调基本上可以取代自己,可以这么说.liftCallback只是将一个普通函数提升到我的类型,同时runCallback使用一个IORef转换Callback为一个简单的函数.

该类型的一般结构是:

data T m a = T (a -> m (T m a))
Run Code Online (Sandbox Code Playgroud)

看起来很像这可能与类别理论中的一些众所周知的数学结构同构.

但它是什么?它是monad还是什么?一个应用函子?一个变形的单子?箭头,甚至?是否有类似Hoogle的搜索引擎可以让我搜索这样的一般模式?

Gab*_*lez 14

您正在寻找的术语是免费的monad变压器.了解这些工作原理的最佳位置是阅读The Monad Reader19期中的"Coroutine Pipelines"文章.Mario Blazevic对这种类型的工作原理进行了非常清晰的描述,除了他称之为"Coroutine"类型.

我在transformers-free包中写了他的类型,然后它被合并到free包中,这是它的新官方主页.

你的Callback类型是同构的:

type Callback a = forall r . FreeT ((->) a) IO r
Run Code Online (Sandbox Code Playgroud)

要理解免费的monad变换器,你需要先了解免费的monad,它们只是抽象的语法树.你给的单子自由仿函数定义的语法树一个单一的步骤,然后创建一个MonadFunctor,基本上是这些类型的步骤清单.所以如果你有:

Free ((->) a) r
Run Code Online (Sandbox Code Playgroud)

这将是一个语法树,它接受零个或多个as作为输入,然后返回一个值r.

但是,通常我们希望嵌入效果或使语法树的下一步依赖于某些效果.为此,我们简单地将我们的免费monad推广到一个免费的monad变换器,它在语法树步骤之间交换基本monad.对于您的Callback类型,您IO在每个输入步骤之间进行交错,因此您的基本monad是IO:

FreeT ((->) a) IO r
Run Code Online (Sandbox Code Playgroud)

关于免费monad的好处是它们是任何仿函数的自动monad,所以我们可以利用它来使用do符号来组装我们的语法树.例如,我可以定义一个await命令来绑定monad中的输入:

import Control.Monad.Trans.Free

await :: (Monad m) => FreeT ((->) a) m a
await = liftF id
Run Code Online (Sandbox Code Playgroud)

现在我有一个用于编写Callbacks 的DSL :

import Control.Monad
import Control.Monad.Trans.Free

printer :: (Show a) => FreeT ((->) a) IO r
printer = forever $ do
    a <- await
    lift $ print a
Run Code Online (Sandbox Code Playgroud)

请注意,我从来没有必要定义必要的Monad实例.双方FreeT fFree f会自动MonadS对于任何函子f,在这种情况下((->) a)是我们的仿函数,所以它会自动做正确的事.这就是范畴理论的神奇之处!

此外,我们从来没有必要定义一个MonadTrans实例才能使用lift. FreeT f在给定任何仿函数的情况下f,它自动成为monad变换器,因此它也为我们处理了这个问题.

我们的打印机是合适的Callback,所以我们可以通过解构免费的monad变换器来提供它的价值:

feed :: [a] -> FreeT ((->) a) IO r -> IO ()
feed as callback = do
    x <- runFreeT callback
    case x of
        Pure _ -> return ()
        Free k -> case as of
            []   -> return ()
            b:bs -> feed bs (k b)
Run Code Online (Sandbox Code Playgroud)

实际打印发生在我们绑定时runFreeT callback,然后我们在语法树中给出了下一步,我们将提供列表的下一个元素.

我们来试试吧:

>>> feed [1..5] printer
1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

但是,您甚至不需要自己编写所有这些内容.正如彼得指出的那样,我的pipes图书馆为你提取了这样的常见流媒体模式.你的回调只是:

forall r . Consumer a IO r
Run Code Online (Sandbox Code Playgroud)

我们定义printer使用的方式pipes是:

printer = forever $ do
    a <- await
    lift $ print a
Run Code Online (Sandbox Code Playgroud)

...我们可以为它提供一个值列表,如下所示:

>>> runEffect $ each [1..5] >-> printer
1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

我的设计pipes包含了大量的这类流式抽象,你总是可以使用do符号来构建每个流组件. pipes还提供各种优雅的解决方案,用于处理状态和错误处理以及双向信息流,因此如果您根据需要制定Callback抽象pipes,您可以免费使用大量有用的机器.

如果您想了解更多信息pipes,我建议您阅读本教程.


dav*_*420 8

这种类型的一般结构在我看来就像

data T (~>) a = T (a ~> T (~>) a)
Run Code Online (Sandbox Code Playgroud)

其中,(~>) = Kleisli m在条件(箭头).


Callback它本身看起来不像我能想到的任何标准Haskell类型类的实例,但它是一个Contravariant Functor(也称为Cofunctor,因为事实证明是错误的).因为它没有包含在GHC附带的任何库中,所以在Hackage上存在一些定义(使用这个),但它们看起来像这样:

class Contravariant f where
    contramap :: (b -> a) -> f a -> f b
 -- c.f. fmap :: (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

然后

instance Contravariant Callback where
    contramap f (Callback k) = Callback ((fmap . liftM . contramap) f (f . k))
Run Code Online (Sandbox Code Playgroud)

是否有一些来自类别理论的异域结构Callback?我不知道.


小智 6

我认为这种类型非常接近我所听到的称为"电路"的电路,这是一种箭头.暂时忽略IO部分(我们可以通过转换Kliesli箭头来实现这一点)电路变压器是:

newtype CircuitT a b c = CircuitT { unCircuitT :: a b (c, CircuitT a b c) }
Run Code Online (Sandbox Code Playgroud)

这是一个箭头,它返回一个新箭头,每次用于下一个输入.只要基本箭头支持它们,就可以为此箭头变换器实现所有常见的箭头类(包括循环).现在,我们所要做的就是让它在理论上与你提到的类型相同,就是摆脱额外的输出.这很容易完成,因此我们发现:

Callback a ~=~ CircuitT (Kleisli IO) a ()
Run Code Online (Sandbox Code Playgroud)

好像我们看右边:

CircuitT (Kleisli IO) a () ~=~
  (Kliesli IO) a ((), CircuitT (Kleisli IO) a ()) ~=~
  a -> IO ((), CircuitT (Kliesli IO) a ())
Run Code Online (Sandbox Code Playgroud)

从这里开始,您可以看到它与Callback a类似,除了我们还输出一个单位值.由于单位价值在其他东西的元组中,这实际上并没有告诉我们多少,所以我会说它们基本相同.

NB出于某种原因,我使用〜=〜表示类似但不完全相同.它们非常相似,特别注意我们可以将a转换Callback a为a CircuitT (Kleisli IO) a (),反之亦然.

编辑:我也完全同意这是A)一个monadic costream(monadic操作特别是无限数量的值,我认为这意味着)和B)一个仅消耗管道(在很多方面非常类似于电路类型没有输出,或者输出设置为(),因为这样的管道也可能有输出).