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 Reader第19期中的"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,它们只是抽象的语法树.你给的单子自由仿函数定义的语法树一个单一的步骤,然后创建一个Monad从Functor,基本上是这些类型的步骤清单.所以如果你有:
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 f并Free 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,我建议您阅读本教程.
这种类型的一般结构在我看来就像
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)一个仅消耗管道(在很多方面非常类似于电路类型没有输出,或者输出设置为(),因为这样的管道也可能有输出).