我一直在使用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
必须有一个我缺少的一般模式。你能帮我解决一下吗?
答案 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
接下来,我介绍两种类型:Open
和Close
。 Close
只是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
,然后应用kp
或kf
,具体取决于我们得到了什么。它确实有效:
matchF
:: Functor f
=> (a -> r)
-> (f (F f a) -> r)
-> F f a
-> r
matchF kp kf = either kp kf . open
回到关于自然数的原始评论:当我们可以合理地期望简单的案例分析是恒定时间时,使用教会数字实现的前任是自然数的大小的线性。好吧,就像自然数字一样,这个案例分析非常昂贵,因为正如在runF
的定义中使用open
所示,遍历了整个结构。