哈斯克尔有没有态射?

时间:2011-08-14 16:08:46

标签: haskell functional-programming functor

我有一些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,它应该叫做态射)?

5 个答案:

答案 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')

此处,vla为您的Term - 形成操作定义了另一个代数,仅作用于{{1而不是y,解释如何处理变量,lambda和应用程序。对于某些合适的monad Term x,您可以选择ym (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 vl一样)解释了如何处理其子域已被处理为a类型值的任何术语。通过明确表示一个层由哪个层组成,我们可以逐层挖掘工作的一般模式。

答案 1 :(得分:7)

看看the uniplate package。它可以进行你正在谈论的那种转换,但对于任意数据结构。

答案 2 :(得分:4)

使用您在问题中引用的fmap',转换函数f只能将Var值转换为不同的lambda表达式。它不能将LambdaApply转换为其他内容,因为它在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)

您可能会发现unbound包非常有用,尤其是substs功能。