Haskell 中有态射吗?
我有一些 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 >,它可能应该称为态射)?
I have some GADT which represents a term in the lambda-calculus.
data Term a =
Var a
| Lambda a (Term a)
| Apply (Term a) (Term a)
What I want to do is to have a general interface for transformation on that type. It should have type signature similar to this:
(Term a -> Term a) -> Term a -> Term a
It's easy to write this function:
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)
So, my question is there some kind of general structure in haskell (or haskell library) to make this kind of transformations (similar to Functor
, it should probably called morphism)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
作为参考,这里是术语...
(我注意到变量的表示是抽象的,这通常是一个很好的计划。)
...这是建议的函数。
这个定义让我困扰的是
f
只适用于(Var v)
形式的术语,所以你不妨实现替换 。如果您稍微注意区分绑定变量和自由变量,您就可以将
Term
设为Monad
,并通过替换实现(>>=)
。一般来说,术语可以具有用于重命名的Functor
结构和用于替换的Monad
结构。 Bird 和 Paterson 有一篇可爱的论文那个,但我离题了。同时,如果您确实想要执行除 at 变量之外的操作,一种通用方法是使用通用遍历工具包,如 uniplate,如 augustss 建议的那样。另一种可能,也许稍微更有纪律,是根据您的类型使用“折叠”。
在这里,
v
、l
和a
为您的Term
定义了一个替代代数 -形成操作,仅作用于y
,而不是Term x
,解释如何处理变量、lambda 和应用程序。对于某些合适的单子m
(例如,为变量线程化环境),您可以选择y
为m(Term x)
,而不是只是Term x
本身。每个子项都经过处理以给出y
,然后选择适当的函数来生成整个项的y
。折叠捕获标准递归模式。普通的一阶数据类型(以及一些表现良好的高阶数据类型)都可以配备折叠运算符。以牺牲可读性为代价,您甚至可以一劳永逸地编写折叠运算符。
与递归
Term a
不同,此TermF a t
解释了如何使用t
元素创建术语的一层在地下的地方。我们通过使用递归Fix
类型取回递归Term
结构。我们在外观上损失了一点,因为每一层都有一个额外的In
来包裹它。我们可以定义,但不能在模式匹配中使用这些定义。不过,回报是我们可以使用通用的
fixFold
而无需额外费用。要根据项计算y
,我们只需要给出一个类型为的函数(就像
v
、l
和a上面的
)解释了如何处理其子项已被处理为y
类型值的任何项。通过明确一层的类型,我们可以利用逐层工作的一般模式。For reference, here are the terms...
(I note that the representation of variables is abstracted, which is often a good plan.)
...and here is the proposed function.
What bothers me about this definition is that
f
is only ever applied to terms of form(Var v)
, so you might as well implement substitution.If you took slightly more care to distinguish bound from free variables, you'd be able to make
Term
aMonad
with substitution implementing(>>=)
. In general, terms can have aFunctor
structure for renaming and aMonad
structure for substitution. There's a lovely paper by Bird and Paterson about that, but I digress.Meanwhile, if you do want to act other than at variables, one general approach is to use general purpose traversal toolkits like uniplate, as augustss suggests. Another possibility, perhaps slightly more disciplined, is to work with the ‘fold’ for your type.
Here,
v
,l
anda
define an alternative algebra for yourTerm
-forming operations, only acting ony
, rather thanTerm x
, explaining how to handle variables, lambdas and applications. You might choosey
to bem (Term x)
for some suitable monadm
(e.g., threading an environment for the variables), rather than justTerm x
itself. Each subterm is processed to give ay
, then the appropriate function is chosen to produce they
for the whole term. The fold captures the standard recursion pattern.Ordinary first-order datatypes (and some well-behaved higher-order datatypes) can all be equipped with fold-operators. At a cost to readability, you can even write the fold operator once and for all.
Unlike your recursive
Term a
, thisTermF a t
explains how to make one layer of a term, witht
elements in the subterm places. We get back the recursiveTerm
structure by using the recursiveFix
type. We lose a little cosmetically, in that each layer has an extraIn
wrapping it. We can definebut we can't use those definitions in pattern matching. The payoff, though, is that we can use the generic
fixFold
at no extra cost. To compute ay
from a term, we need only give a function of typewhich (just like
v
,l
, anda
above) explains how to handle any term whose subterms have already been processed to values of typey
. By being explicit in types about what one layer consists of, we can tap into the general pattern of working layer by layer.查看uniplate 包。它可以进行您正在讨论的那种转换,但适用于任意数据结构。
Have a look at the uniplate package. It can do the kind of transformations you are talking about, but for an arbitrary data structure.
使用问题中引用的
fmap'
,转换函数f
只能将Var
值转换为不同的 lambda 表达式。它无法将Lambda
或Apply
转换为其他内容,因为它是在fmap'
函数中硬编码的,这些构造函数保持不变。例如,您的
fmap'
可以将Var 1
转换为Lambda 1 (Var 1)
,但反之则不然。这真的是你的意图吗?
如果
fmap'
允许任意转换,您最终会得到以下结果:这与
$
相同。(另一方面,如果只允许
fmap'
更改表达式中的值,而根本不更改其结构,那么您将回到通常的fmap
.)With the
fmap'
you quoted in the question, the transformation functionf
can only transformVar
values to different lambda expressions. It cannot transform aLambda
orApply
into something else, since it's hardcoded in thefmap'
function that these constructors stay the same.Your
fmap'
can for example transformVar 1
intoLambda 1 (Var 1)
, but not the other way around.Is this really your intention?
If
fmap'
should allow arbitrary transformations, you end up with this:This is the same as
$
.(If, on the other hand,
fmap'
should only be allowed to change the values in the expression, without changing its structure at all, you would be back at the usualfmap
.)首先,我没有你的实际问题的答案,尽管我确实有一些可能对你有用的想法,但不确定。
在我看来,这并不那么普遍,我希望您使用类似的东西:
它仍然允许您使用相同的功能,您只需在 f 本身内进行模式匹配。我认为这个函数可以用于诸如计算 lambda 演算之类的事情(不过,您需要随身携带一些状态)。
将来对您可能更有用的东西可能是:
您可以稍后将其与 State monad 一起使用来跟踪 lambda 的绑定。此外,当您使用 Identity monad 时,此函数与上面的函数相同,您可以编写一个简单的函数,采用 f :: (Term a -> Term a) 并使用 fmap' f = fmapM (f.(return :: ->身份a))。
让我知道这是否有帮助:)
Firstly, I don't have an answer to your actual question, though I do have some ideas that might be useful to you, not sure.
That seems to me to be less general than it might be, I'd expect you to use something like:
which still allows you the same functionality, you just do pattern matching within f itself. I think this function can be used to things like evaluation of your lambda calculus (though, there's some state you need to bring around with you).
Something that might be more useful in the future for you might be:
Which you can then use later with a State monad to keep track of bindings from lambdas. Also this function is the same as the one above when you use the Identity monad, you could write a simple function taking f :: (Term a -> Term a) and use fmap' f = fmapM (f.(return :: -> Identity a)).
Let me know if this is helpful :)
您可能会发现 unbound 包很有用,尤其是 substs 函数。
You may find the unbound package useful, especially the substs function.