Function 什么是多态lambda?
lambdas(匿名函数)的概念对我来说非常清楚。我知道类的多态性,运行时/动态调度用于根据实例最派生的类型调用适当的方法。但lambda究竟是如何多态的呢?我是另一个试图学习函数式编程的Java程序员。您是否听说过术语“多态lambda”?我们也许可以更具体一些 lambda具有多态性的最简单方法是接受类型与最终结果(部分)无关的参数 e、 g.兰姆达Function 什么是多态lambda?,function,haskell,lambda,functional-programming,polymorphism,Function,Haskell,Lambda,Functional Programming,Polymorphism,lambdas(匿名函数)的概念对我来说非常清楚。我知道类的多态性,运行时/动态调度用于根据实例最派生的类型调用适当的方法。但lambda究竟是如何多态的呢?我是另一个试图学习函数式编程的Java程序员。您是否听说过术语“多态lambda”?我们也许可以更具体一些 lambda具有多态性的最简单方法是接受类型与最终结果(部分)无关的参数 e、 g.兰姆达 \(head:tail) -> tail 具有类型[a]->[a]——例如,它在列表的内部类型中是完全多态的 其他简单的例子有 \_
\(head:tail) -> tail
具有类型[a]->[a]
——例如,它在列表的内部类型中是完全多态的
其他简单的例子有
\_ -> 5 :: Num n => a -> n
\x f -> f x :: a -> (a -> b) -> b
\n -> n + 1 :: Num n => n -> n
等等
(注意涉及typeclass分派的Num n
示例)您会注意到,在下面的回答中,我没有太多地谈论lambdas。请记住,在函数式语言中,任何函数都只是一个绑定到名称的lambda,因此我所说的函数都会转换为lambda
多态性
请注意,多态性实际上并不需要OO语言通过重写虚拟方法的派生类实现的那种“分派”。那太好了
多态性本身只是意味着函数不仅允许一种特定类型的参数,而且能够对任何允许的类型进行相应的操作。最简单的例子是:您根本不关心类型,只需将传入的内容交给用户即可。或者,为了使它不那么琐碎,将其包装在单个元素容器中。您可以在C++中实现这样一个函数:
template<typename T> std::vector<T> wrap1elem( T val ) {
return std::vector(val);
}
这里,void*
只是指向任何未知类型的指针。因此出现了一个明显的问题:vector
不知道它“包含”什么类型的元素!所以你不能用这些东西做任何有用的事情。除非你知道它是什么类型
除了仍然很麻烦之外,上面所有的C代码都有一个巨大的问题:如果容器元素的类型正确,那么它是完全未检查的!来自*void
的强制类型转换将愉快地在任何类型上开火,但毫无疑问,结果将是完全的垃圾2
类与继承
这个问题是OO语言解决的主要问题之一,它试图将您可能执行的所有操作与对象中的数据作为方法捆绑在一起。编译类时,类型是单态的,因此编译器可以检查操作是否有意义。当您尝试使用这些值时,只要编译器知道如何找到该方法就足够了。特别是,如果您创建了一个派生类,编译器就会知道“啊哈,即使在派生对象上也可以从基类调用该方法”
不幸的是,这意味着您通过多态性实现的所有功能都相当于合成数据并在单个字段上简单地调用(单态)方法。为了对不同的类型获得不同的行为(但有控制地),OO语言需要虚拟方法。这基本上意味着该类有额外的字段,这些字段带有指向方法实现的指针,这与我在C示例中使用的combine
函数的指针非常相似,不同之处在于只能通过添加派生类来实现重写方法,编译器再次知道所有数据字段等的类型,因此您是安全的
复杂的类型系统,检查参数多态性
虽然基于继承的多态性显然有效,但我还是忍不住要说,这简直是愚蠢至极,确实有点限制。如果只想使用一个碰巧没有实现为类方法的特定操作,则需要生成一个完整的派生类。即使您只是想以某种方式改变一个操作,您也需要派生并重写一个稍微不同的方法版本
让我们重温一下我们的C代码。从表面上看,我们注意到它应该完全可以使它成为类型安全的,而不需要任何捆绑无用信息的方法。我们只需要确保没有类型信息丢失——至少在编译时不会丢失。想象(读)∀T为“所有类型的T”)
并且试着去做
vector<float> v; char acc;
accum_contents(v, evil_sumon, acc);
1与奇怪的显式
malloc
stuff、类型安全性等不同:在没有垃圾收集器的语言中,这样的代码非常难处理,因为一旦不再需要内存,总有人需要清理内存,但是如果你没有注意到是否有人仍然持有对数据的引用,并且实际上可能仍然需要它。在Java、Lisp、Haskell中,这没什么好担心的
2对此有一种完全不同的方法:动态语言选择的方法。在这些语言中,每个操作都需要确保它可以与任何类型一起工作(或者,如果不可能,则引发定义良好的错误)。然后,您可以任意组合多态操作,这一方面“非常无故障”(不像Haskell这样非常聪明的类型系统那样无故障),但OTOH会带来相当大的开销,因为即使是原始操作也需要类型决策和保护措施
我在这里当然不公平。OO范式不仅仅是类型安全多态性,它还支持许多事情,例如旧ML和它的Hindler-Milner类型系统不能做的事情(即席多态性:Haskell有类型类,SML有模块),甚至有些事情在Haskell中非常困难(主要是在一个可变大小的容器中存储不同类型的值). 但是,你对函数编程的习惯越少,你对这些东西的需求就越少。C++(C++)多态(或通用)lambda的 < p>是一个可以以任何类型作为参数的lambda。基本上,它是一个具有auto
参数类型的lambda:
autolambda=[](auto){}代码>我猜这是因为lambda可以处理“gener”
int sum_contents_int(vector v) {
int acc = 0, i;
for(i=0; i<v.size; ++i) {
acc += * (int*) (v.contents[i]);
}
return acc;
}
int accum_contents_int(vector v, void* (*combine)(int*, int)) {
int acc = 0, i;
for(i=0; i<v.size; ++i) {
combine(&acc, * (int*) (v.contents[i]));
}
return acc;
}
void multon(int* acc, int x) {
acc *= x;
}
int main() {
int a = 3, b = 5;
vector v = wrap2elems(a, b);
printf("%i\n", accum_contents_int(v, multon));
}
∀T: {
typedef struct{
T* contents;
int size;
} vector<T>;
}
∀T: {
vector<T> wrap1elem(T* elem) {
vector v;
v.contents = malloc(sizeof(T*));
v.contents[0] = &elem;
v.size = 1;
return v;
}
}
∀T: {
void accum_contents(vector<T> v, void* (*combine)(T*, const T*), T* acc) {
int i;
for(i=0; i<v.size; ++i) {
combine(&acc, (*T) (v[i]));
}
}
}
void evil_sumon (int* acc, double* x) { acc += *x; }
vector<float> v; char acc;
accum_contents(v, evil_sumon, acc);
\_ (xl, xr) -> (xr, xl) :: ∀ a t. a -> (t,t) -> (t,t)