各种编程语言中的 Lambda
编程语言/Lambda| 2024-01-11
我喜欢看 Conor Hoekstra 的视频,一方面是因为他是一位引人入胜的主持人,另一方面是因为他介绍了很多很多编程语言。我不懂那么多语言,所以能接触到不同语言如何解决相同的问题是件好事。
他最近的一段视频讨论了一个相当简单的问题(如何计算矩阵中负数的个数),他用了十几种语言来实现这个问题。所有语言都必须有某种机制来确定一个数字是否为负数–对于大多数语言来说,这涉及到使用 lambda(有时称为匿名函数)。
我发现特别有趣的是所有不同语言的 lambda 看起来都是怎样的,因此我想特别关注这一点。在特定的语言中,你会如何编写一个 lambda 表达式来检查给定的数字是否为负数?
(<0) // 4: Haskell
_ < 0 // 5: Scala
_1 < 0 // 6: Boost.Lambda
#(< % 0) // 8: Clojure
&(&1 < 0) // 9: Elixir
|e| e < 0 // 9: Rust
\(e) e < 0 // 10: R 4.1
{ $0 < 0 } // 10: Swift
{ it < 0 } // 10: Kotlin
e -> e < 0 // 10: Java
e => e < 0 // 10: C#, JS, Scala
\e -> e < 0 // 11: Haskell
{ |e| e < 0 } // 13: Ruby
{ e in e < 0 } // 14: Swift
{ e -> e < 0 } // 14: Kotlin
fun e -> e < 0 // 14: F#, OCaml
lambda e: e < 0 // 15: Python
(λ (x) (< x 0)) // 15: Racket
fn x -> x < 0 end // 17: Elixir
(lambda (x) (< x 0)) // 20: Racket/Scheme/LISP
[](auto e) { return e < 0; } // 28: C++
std::bind(std::less{}, _1, 0) // 29: C++
func(e int) bool { return e < 0 } // 33: Go
对于需要使用大括号的语言,我会计算大括号的数量(无论这是否公平)。另外,Clojure 可以使用 %1 代替 %。
是的,请注意,Boost.Lambda 和 std::bind 也在该列表中(并假设您在占位符的作用域中使用using namespace声明)。
我认为这张表本身就很有趣。它基本上说明了这里确实有三种 lambda:
完全匿名函数(这些 lambdas 接受某种参数列表,然后有一个独立的主体,如 C++ 或 Java 或……)
占位符表达式(带有特殊占位符的单一表达式,如 Scala、Clojure 或 Boost.Lambda 或…)。在这方面,Swift 似乎是独一无二的,它使用 $0 作为第一个参数,而我熟悉的所有其他语言和库都是从 1 开始计数。
Partial 函数应用(从技术上讲实际上不是 lambdas,但解决了相同的问题,所以足够接近,如 Haskell 或 std::bind)
有几种语言在这里也有多个选项。
值得注意的是,C++ 的 lambda 几乎是最长的 lambda。这有点令人惊讶, C++ 的 lambda 之所以长,是因为 C++ 的基本复杂性——Go 的借口是什么?所以这很酷。技术上不是最后一个!
不过,这对 C++ 来说是一个有利的比较,因为我们都在获取一个值并返回一个值。如果我们需要获取一个引用,那就需要使用 auto const& 或 auto&&(长 7 或 2 个字符)。如果我们想返回一个引用而不是一个值呢?那就使用 -> decltype(auto),这样就多了 17 个字符,和其他 lambda 一样长。
C++ 的 lambdas 有三个部分在这套语言中是独一无二的,或者说大部分是独一无二的:
指定捕获。例如,Rust 允许通过move来捕获,写成 move |needle|haystack.contains(needle)。正如用户 Nobody_1707 在 reddit 上指出的,Swift 也有与 C++ 相当类似的捕获。但除此之外,我不确定其他语言是否有捕获的概念。基本上就是 [&]。话虽如此,鉴于 C++ 没有垃圾回收,我不确定除了 [] 之外还有什么好的捕获默认设置,而在这一点上,我们并不能节省很多字符。
强制性参数声明。在许多其他静态类型语言中,你可以提供类型注解,但它是可选的。在 Rust 中,例子可以是 |e: i32| e < 0,就像在 Scala 中,可以是 (e: Int) => e < 0。在简单的情况下,你可能会避免使用类型,而在更复杂的情况下,你可能更愿意保留它。
return 关键字。在其他语言中,我们只有一个表达式。
我曾试图提出的一个建议(P0573)可以创建一种新形式的 “完全匿名函数”,使参数声明成为可选项,并省略返回关键字。该文件建议:
[](e) => e < 0 // P0573R2: 14
[](auto e) { return e < 0; } // C++: 28
这样长度就减少了一半。虽然还是比其他大多数语言长,但已经好很多了。然而,这个提议由于一些显著的原因被否决了:不同的解析问题(人类对未命名参数的歧义)和返回类型的含义。请参阅我之前关于该主题的文章。我认为取消类型注释对 C++ 来说之所以困难,部分原因在于我们的参数声明是 Type name 形式,而许多其他语言则将其写成 name: Type – 后者更适合省略类型并将重点放在名称上(而且不允许使用未命名参数,这是 C++ 问题的关键所在,反正我从来没觉得这是一个特别重要的特性)。
因此,我认为 “完全匿名函数 “的新语法可能不会被提出来–我不知道如何用类似 C++ lambdas 的语法来克服这两个问题(尽管对于在Prague 提出的第三个问题,P2036 得到了很好的回应,而且似乎有可能作为缺陷被接受)。在我看来,为完整的 lambdas 引入不同的语法目前并不可取(无论如何,仍然会有 auto 与 decltype(auto) 的问题)。
但这样一来,占位符表达式的问题就悬而未决了。我原本有些嘲笑这种样式,认为它比完整的匿名函数样式更难读。但对于简单的情况,我不再那么肯定了。正如 vector
这种语法可能是什么样的呢?我们仍然希望保留 “捕获 “的概念–我认为这仍然是 C++ 中的一个重要概念,而且无论如何我们都需要一个引入者。这样做的原因是,考虑一下:f(_1)。这意味着什么?
f([](auto&& x, auto&&...) -> decltype(auto) { return (x); })
或
[](auto&& x, auto&&...) -> decltype(auto) { return f(x); }
如果这取决于背景……那么,你如何决定?这似乎是个难题。老实说,我并不完全确定 Scala 是怎么做的。Clojure、Elixir 和 Swift 对于 lambda 的起始位置都有明确的标记。而且我不认为我们真的可以在这里使用大括号–比如 { f(_1) }。
也许我们可以使用引入符,然后是某种标点符号,最后是表达式?
[] => _1 < 0
[] -> _1 < 0
[]: _1 < 0
这当然有所不同,但基本上与 Boost.Lambda 中的功能相同(只是可能产生更好的代码)。
在 HOPL 论文中有一个演示 STL.Lambda 的例子:
vector
find_if(v.begin(), v.end(), Less_than
考虑一下这里函数对象的形状–这是一个partial 函数应用。这正是我们在 Haskell 中会写的 (< "falcon")(无论如何,从语义上讲)。比约恩-法勒(Björn Fahller)有一个完整的资源库,其中的函数对象都支持类似的partial 函数应用,唯一不同的是,他的版本去掉了类型:less_than("falcon")。
现在,与之对应的 C++ lambda 会是什么呢?
[](std::string const& s) { return s < "f"; } // 44: C++11
[](auto&& s) { return s < "f"; } // 32: C++14
lift::less_than("f") // 20: with lift
这就是为什么我经常编写泛型 lambda,即使我只需要单态的 lambda。我不得不缩短字符串,因为 lambda 太宽了,我的博客放不下!谢谢你,费萨尔!
如果这种风格对两个编程风格迥异的人(尽管他们的名字中大部分字母都相同)来说已经足够好了,那么也许参数名无论如何都被高估了?我的意思是,它确实读起来很不错:find_if(..., less_than(...)) 是很不错的英文。如果我们用运算符代替单词,真的会有什么不同吗?
(<"f") // 6: Haskell
[]: _1 < "f" // 12: placeholder?
lift::less_than("f") // 20: with lift
[](auto&& s) { return s < "f"; } // 32: C++14
我可以习惯这一点。我并不觉得这会造成永久性的困惑。
当然,作为 C++,还有很多其他问题需要考虑。比如,如何处理转发(使用宏),如何处理可变参数(我不知道),这些 lambdas 的 arity 是什么,是基于存在的最大占位符吗(不是),还是需要 P0834 来处理这个问题(问得好),或者像 P0119 中建议的那样,使用更简短的形式来指定操作符函数(是的,特别是 (>) 语法,作为 std::greater() 的更简短写法)。
不过,这已经完全偏离了本篇文章的主旨,那就是:C++ 的 lamb 长度真的非常非常长:C++ 的 lambdas 真的非常非常长。
本文文字及图片出自 Lambda Lambda Lambda
你对本文的反应是:
0
俺的神呀 0
赞一个 0
飘过~ 0
强 0
很实用 0
好文 0
笑死了 0
mark 0
敬佩 0
垃圾 0
0
看样子你已经点过这个了!
抱歉,你最多只能点三个!
你也许感兴趣的:
Rust 比 C 更快吗?
【程序员搞笑图片】Rust:愿者上钩
对 Rust 10 年的押注以及我对未来的期待
Java 30 年:一门为失败的小工具设计的语言如何成为全球强势语言
Rust 10 周年:一部破电梯如何彻底改变了软件
世界末日的最佳编程语言
编程语言的选择
Julia 的新天地
【程序员搞笑图片】数据类型简明指导
33 种编程语言的 UUIDv7 实现
发表回复 取消回复您的邮箱地址不会被公开。 必填项已用 * 标注
评论 *
显示名称 *
邮箱 *
网站