我有一些GADT代表lambda演算中的一个术语。
data Term a =
Var a
| Lambda a (Term a)
| Apply (Term a) (Term a)
我想要做的是在该类型上进行转换的通用接口。它应该具有类似于此的类型签名:
(Term a -> Term a) -> Term a -> Term a
编写此功能很容易:
fmap' :: (Term a → Term a) → Term a → Term a
fmap' f (Var v) = f (Var v)
fmap' f (Lambda v t) = Lambda v (fmap' f t)
fmap' f (Apply t1 t2) = Apply (fmap' f t1) (fmap' f t2)
所以,我的问题是haskell(或haskell库)中有某种通用结构来进行这种转换(类似于Functor
,它应该叫做态射)?
答案 0 :(得分:12)
供参考,以下是术语......
data Term a =
Var a
| Lambda a (Term a)
| Apply (Term a) (Term a)
(我注意到变量的表示是抽象的,这通常是一个很好的计划。)
......这是建议的功能。
fmap' :: (Term a → Term a) → Term a → Term a
fmap' f (Var v) = f (Var v)
fmap' f (Lambda v t) = Lambda v (fmap' f t)
fmap' f (Apply t1 t2) = Apply (fmap' f t1) (fmap' f t2)
令我困惑的是,f
只适用于(Var v)
形式的术语,因此您也可以实现替换。
subst :: (a → Term a) → Term a → Term a
subst f (Var v) = f v
subst f (Lambda v t) = Lambda v (subst f t)
subst f (Apply t1 t2) = Apply (subst f t1) (subst f t2)
如果您稍微小心区分绑定与自由变量,则可以使Term
成为Monad
,替换实现(>>=)
。通常,术语可以具有用于重命名的Functor
结构和用于替换的Monad
结构。 Bird and Paterson关于这一点有一篇可爱的论文,但我离题了。
同时,如果你做想要在变量之外采取行动,一种通用方法是使用通用遍历工具包,如uniplate,正如augustss建议的那样。另一种可能性,也许是稍微有点自律,是为你的类型使用'折叠'。
tmFold :: (x -> y) -> (x -> y -> y) -> (y -> y -> y) -> Term x -> y
tmFold v l a (Var x) = v x
tmFold v l a (Lambda x t) = l x (tmFold v l a t)
tmFold v l a (Apply t t') = a (tmFold v l a t) (tmFold v l a t')
此处,v
,l
和a
为您的Term
- 形成操作定义了另一个代数,仅作用于{{1而不是y
,解释如何处理变量,lambda和应用程序。对于某些合适的monad Term x
,您可以选择y
为m (Term x)
(例如,为变量设置线程),而不仅仅是m
本身。处理每个子项以给出Term x
,然后选择适当的函数以产生整个项的y
。折叠捕获标准递归模式。
普通的一阶数据类型(以及一些表现良好的高阶数据类型)都可以配备fold-operators。以可读性为代价,您甚至可以一劳永逸地编写折叠运算符。
y
与递归data Fix f = In (f (Fix f))
fixFold :: Functor f => (f y -> y) -> Fix f -> y
fixFold g (In xf) = g (fmap (fixFold g) xf)
data TermF a t
= VarF a
| LambdaF a t
| ApplyF t t
type Term a = Fix (TermF a)
不同,此Term a
解释了如何在子项中使用TermF a t
元素创建术语的一个层。我们使用递归t
类型返回递归Term
结构。我们失去了一点美观,因为每一层都有一个额外的Fix
包裹它。我们可以定义
In
但我们不能在模式匹配中使用这些定义。但是,回报是我们可以使用通用var x = In (VarF x)
lambda x t = In (LambdaF x t)
apply t t' = In (Apply x t t')
而无需额外费用。要从术语计算fixFold
,我们只需要提供类型为
y
(就像上面的TermF a y -> y
,v
和l
一样)解释了如何处理其子域已被处理为a
类型值的任何术语。通过明确表示一个层由哪个层组成,我们可以逐层挖掘工作的一般模式。
答案 1 :(得分:7)
看看the uniplate package。它可以进行你正在谈论的那种转换,但对于任意数据结构。
答案 2 :(得分:4)
使用您在问题中引用的fmap'
,转换函数f
只能将Var
值转换为不同的lambda表达式。它不能将Lambda
或Apply
转换为其他内容,因为它在fmap'
函数中是硬编码的,这些构造函数保持不变。
您的fmap'
可以将Var 1
转换为Lambda 1 (Var 1)
,但不是相反。
这真的是你的意图吗?
如果fmap'
应允许任意转换,您最终会得到:
fmap' :: (Term a → Term a) → Term a → Term a
fmap' f t = f t
这与$
相同。
(另一方面,如果只允许fmap'
更改表达式中的值,而根本不更改其结构,则会返回到通常的fmap
。< / p>
答案 3 :(得分:2)
首先,我对你的实际问题没有答案,但我确实有一些可能对你有用的想法,不确定。
在我看来,这种情况不太普遍,我希望你能使用类似的东西:
fmap' :: (Term a → Term a) → Term a → Term a
fmap' f (Var v) = f $ Var v
fmap' f (Lambda v t) = f $ Lambda v (fmap' f t)
fmap' f (Apply t1 t2) = f $ Apply (fmap' f t1) (fmap' f t2)
仍然允许您使用相同的功能,您只需在f本身内进行模式匹配。我认为这个函数可以用来评估你的lambda演算(不过,你需要带一些状态)。
将来可能更有用的东西可能是:
fmapM :: Monad m => (Term a -> m (Term a)) -> Term a -> m (Term a)
fmapM f (Var v) = f (Var v)
fmapM f (Lambda v t) = do
t' <-fmapM f t
f (Lambda v t')
fmapM f (Apply t1 t2) = do
t1' <- fmapM f t1
t2' <- fmapM f t2
f (Apply t1' t2')
然后您可以使用State monad来跟踪lambdas的绑定。当你使用Identity monad时,这个函数与上面的函数相同,你可以写一个简单的函数,取f ::(术语a - &gt;术语a)并使用fmap'f = fmapM(f。(return ::) - &gt;身份a))。
让我知道这是否有用:)
答案 4 :(得分:1)