如何使用免费Monads的Church编码?

时间:2015-07-14 05:45:45

标签: haskell free-monad church-encoding scott-encoding

我一直在使用Free包中Control.Monad.Free的{​​{1}}数据类型。现在我尝试将其转换为在free中使用F,但无法弄清楚如何映射函数。

例如,使用Control.Monad.Free.Church的简单模式匹配函数看起来像这样 -

Free

我可以通过转换为/ -- Pattern match Free matchFree :: (a -> r) -> (f (Free f a) -> r) -> Free f a -> r matchFree kp _ (Pure a) = kp a matchFree _ kf (Free f) = kf f 来轻松将其转换为使用F的函数 -

Free

但是,如果不使用-- Pattern match F (using toF and fromF) matchF :: Functor f => (a -> r) -> (f (F f a) -> r) -> F f a -> r matchF kp kf = matchF' . fromF where matchF' (Pure a) = kp a matchF' (Free f) = kf (fmap toF f) toF -

,我无法弄清楚如何完成工作
fromF

必须有一个我缺少的一般模式。你能帮我解决一下吗?

3 个答案:

答案 0 :(得分:5)

让我描述一个更简单的场景 - 列表的区别。让我们关注如何使用列表:

  • 通过catamorphism,这实际上意味着我们可以使用

    来表达它
    foldr :: (a -> r -> r) -> r -> [a] -> r
    

    正如我们所看到的,折叠函数永远不会得到列表尾部,只有它的处理值。

  • 通过模式匹配我们可以做得更多,特别是我们可以构造一个类型的广义折叠

    foldrGen :: (a -> [a] -> r) -> r -> [a] -> r
    

    很容易看到人们可以使用foldr表达foldrGen。但是,由于foldrGen不是递归的,因此该表达式涉及递归。

  • 为了概括这两个概念,我们可以介绍

    foldrPara :: (a -> ([a], r) -> r) -> r -> [a] -> r
    

    赋予消耗功能更多的力量:尾部的减少值以及尾部本身。显然,这比以前的更通用。这对应于一个paramorphism,它“吃掉它的论点并保持它”。

但也可以反过来做到这一点。尽管paramorphisms更为通用,但它们可以通过重复创建原始结构的方式使用catamorphisms(以一些开销成本)表达:

foldrPara :: (a -> ([a], r) -> r) -> r -> [a] -> r
foldrPara f z = snd . foldr f' ([], z)
  where
    f' x t@(xs, r) = (x : xs, f x t)

现在,教会编码的数据结构对catamorphism模式进行编码,对于列表,它是可以使用foldr构建的所有内容:

newtype List a = L (forall r . r -> (a -> r -> r) -> r)

nil :: List a
nil = L $ \n _ -> n

cons :: a -> List a -> List a
cons x (L xs) = L $ \n c -> c x (xs n c)

fromL :: List a -> [a]
fromL (L f) = f [] (:)

toL :: [a] -> List a
toL xs = L (\n c -> foldr c n xs)

为了查看子列表,我们采用了相同的方法:在途中重新创建它们:

foldrParaL :: (a -> (List a, r) -> r) -> r -> List a -> r
foldrParaL f z (L l) = snd $ l (nil, z) f'
  where
    f' x t@(xs, r) = (x `cons` xs, f x t)

这通常适用于Church编码的数据结构,类似于编码的自由monad。它们表达了catamorphisms,即折叠而不看结构的部分,只有递归结果。为了在过程中掌握子结构,我们需要在途中重新创建它们。

答案 1 :(得分:4)

global_commands = {
"New" : "Starts a fresh game and overwrites any previously saved file.\n\n",
"Load" : "Loads a games state which has already been saved.\n\n",}

看起来像斯科特编码的免费monad。教会编码的版本只是

matchF
  :: Functor f
  => (a -> r)
  -> (f (F f a) -> r)
  -> F f a
  -> r

以下是Church和Scott编码的列表供比较:

matchF
  :: Functor f
  => (a -> r)
  -> (f r -> r)
  -> F f a
  -> r
matchF kp kf f = runF f kp kf

答案 2 :(得分:3)

这有点令人讨厌。这个问题是一个谜题的更一般版本,每个人都会在他们第一次接触它时遇到困难:定义编码为教会数字的自然数的前身(想想:Nat ~ Free Id ())。

我已将模块拆分为许多中间定义,以突出解决方案的结构。我还上传了a self-contained gist以方便使用。

我从没有兴趣开始:重新定义F,因为我目前没有安装此软件包。

{-# LANGUAGE Rank2Types #-}
module MatchFree where

newtype F f a = F { runF :: forall r. (a -> r) -> (f r -> r) -> r }

现在,即使在考虑模式匹配之前,我们也可以从定义通常数据类型构造函数的对应部分开始:

pureF :: a -> F f a
pureF a = F $ const . ($ a)

freeF :: Functor f => f (F f a) -> F f a
freeF f = F $ \ pr fr -> fr $ fmap (\ inner -> runF inner pr fr) f

接下来,我介绍两种类型:OpenCloseClose只是F类型,但Open对应于观察F f a元素的内容:它Either是纯a 1}}或f (F f a)

type Open  f a = Either a (f (F f a))
type Close f a = F f a

正如我手工描述的那样,这两种类型实际上是等价的,我们确实可以编写在它们之间来回转换的函数:

close :: Functor f => Open f a -> Close f a
close = either pureF freeF

open :: Functor f => Close f a -> Open f a
open f = runF f Left (Right . fmap close)

现在,我们可以回到您的问题,行动过程应该非常明确:open F f a,然后应用kpkf,具体取决于我们得到了什么。它确实有效:

matchF
  :: Functor f
  => (a -> r)
  -> (f (F f a) -> r)
  -> F f a
  -> r
matchF kp kf = either kp kf . open

回到关于自然数的原始评论:当我们可以合理地期望简单的案例分析是恒定时间时,使用教会数字实现的前任是自然数的大小的线性。好吧,就像自然数字一样,这个案例分析非常昂贵,因为正如在runF的定义中使用open所示,遍历了整个结构。