你们平时用匿名函数么?

最近打算学一下 JavaScript,但是习惯 C 系列 java 还有 python 的我看 JS 代码总是看不懂,为什么所有东西都要写成匿名函数?

平时不用匿名函数说明你 Java 和 Python 都用的不够熟。首先匿名函数对于 Java 这种无法嵌套定义函数的语言,可以很方便的捕获局部变量。其次短的函数写成匿名函数可以减少代码冗余

匿名函数就是说它的名字没有意义,或者说我们不关心它的名字。最常用的像是 lambda 表达式,只需要一个小的计算模块,不需要知道名字。js 没有很好的作用域隔离手段,这个时候就可以利用匿名函数将变量或代码放在一个函数里,作为隔离,防止其它代码随便修改变量。还有 JS 的异步编程需要大量的回调函数,回调函数我们也只关心参数和返回值,不关心名字,直接传匿名函数进去比较简洁。如果 JS 学起来比较不习惯,建议学 TypeScript,反正现在你写大一点的 JS 项目肯定要用 TypeScript 的。

确实,我没写过什么复杂的程序

看需要呗,
比如写一个监听事件的函数有没有名字都无所谓就用匿名函数呗

然而 python 一直是不推荐使用lambda. py3.0 那会儿有人建议把lambda这个关键字去掉。虽然没有最终实现,但也把filterbuiltins里面拿掉了。为什么 Python 的lambda故意做成只允许一行,就是为了防止你们这些喜欢匿名函数的程序员滥用。记住,python 程序员应该始终把可阅读性放在第一位。其它的技巧,比如:

def changeName (self, name, save):
    pass

person.changeName ("new name", save=True)

上面那一段写save=True而不是True也是为了可阅读性考虑。

我用 Java 也很少用匿名函数。我喜欢这种写法:

obj.subscribe ("changed", this::updateValue);

这种写法可阅读性更好。

1赞

我没看懂你的论点。匿名函数本身没什么错,只是没有绑定名字而已,在 scheme 等语言里面匿名函数用的很多。

Python 中这个移除 lambda 的建议没通过,说明建议本身就很可笑。你说的 Java 的例子也没有捕获局部变量,如果需要提交十个线程每个带不同参数你这种方法并行不通,而用 lambda 可以参数实例化生成偏函数。如果你喜欢搞一个新 class 来做这个事情,请当我没说,我跟你不是一路人。

适合的情况用函数式有错吗?filter 在有 list comprehension 的情况下确实用处不大。

首先让我们来说一下可阅读性。

lambda 没有绑定名字就是一个很严重的问题。工业代码的阅读性好不好,关键在于它是否长得像自然语言——因为各种主流编程语言都是英文的,所以长得越像英语,使用越少的符号,可阅读性就越强。很明显 Python 比 Java 可阅读性更好,因为它不使用 &&, || 这样的符号而是 and, or,更重要的是 Python 的源代码多余的 token 很少,不会打断思路。

函数式编程语言是另外一个方向,它比较适合拿来描述数学与算法,所以在我看来,函数式编程语言应该尽可能地长得像数学语言——目前没有一个合格的,可能是因为数学家的语言也很随意。

lambda 在我看来是个用于捕获局部变量的小工具。在 java 里面我确实会写一两个匿名函数捕获变量传给回调函数,从来不超过三行。这其实是 java 的缺陷,在这种情况下 ,使用 lambda 代码内部匿名类反而是个进步。这个问题在 Python 里面不存在,因为在 python 里面有更好的 functools.partial,意义更明确。我写作 python 代码可能已经超过二十万行了,基本上没用 lambda.

lambda 是函数式编程范式的产物。几个主流工业语言除了 JS 以前都是没有 lambda 的。在我看来,lambda 的存在很破坏结构化编程范式的可阅读性,性能也不佳。所以我的看法就是 lambda 最好别用。

其次,捕获变量这件事通常与回调相关,需要捕获变量的回调,通常意味着设计问题。Java 就是一个语法不完善的语言,所以需要 lambda. JavaScript 自从有了 async/await 之后,回调一下子少了很多。如果你的代码 lambda 很多,请思考一下是不是有设计上的问题。

最后再说一下 lambda 捕获变量是有坑的,比如在 python 里面,这段代码的结果是什么?

l = []
for i in range (10):
    l.append (lambda : print (i))
for f in l:
    f ()

换成functools.partial (print, i)再试试?

首先让我们来说一下可阅读性。

lambda 没有绑定名字就是一个很严重的问题。工业代码的阅读性好不好,关键在于它是否长得像自然语言——因为各种主流编程语言都是英文的,所以长得越像英语,使用越少的符号,可阅读性就越强。很明显 Python 比 Java 可阅读性更好,因为它不使用 &&, || 这样的符号而是 and, or,更重要的是 Python 的源代码多余的 token 很少,不会打断思路。

Java 是有缺陷的不提了。可读性也是见仁见智的问题,没必要详细讨论。python 缺乏静态类型,类型的可读性差于 Java。加 type annotation 也没有编译器检查,难以保持一致性。对于常用关键字 and or 像不像自然语言其实无妨,因为你不可能忘掉,只是学了一个新的词汇而已。Java 缺陷这么明显,正是因为擅作主张去掉了很多它认为没有用的东西,这本身就是取舍。而 Java 在工业代码里用的规模比 Python 大很多,是不是说所有的 Java 设计都是对的呢?

lambda 在我看来是个用于捕获局部变量的小工具。在 java 里面我确实会写一两个匿名函数捕获变量传给回调函数,从来不超过三行。这其实是 java 的缺陷,在这种情况下 ,使用 lambda 代码内部匿名类反而是个进步。这个问题在 Python 里面不存在,因为在 python 里面有更好的 functools.partial,意义更明确。我写作 python 代码可能已经超过二十万行了,基本上没用 lambda.

功能丰富的语言都可以有不同的完备子集,但是不代表补集真的就没有用了。一个写操作系统底层的人可能永远都用不到面向对象,但是不代表面向对象没有用。functools 的 cpython 实现里面就用了一次 lambda,也许只是你不喜欢用 lambda 而已。参考 cpython/functools.py at 3.8 · python/cpython · GitHub

lambda 是函数式编程范式的产物。几个主流工业语言除了 JS 以前都是没有 lambda 的。在我看来,lambda 的存在很破坏结构化编程范式的可阅读性,性能也不佳。所以我的看法就是 lambda 最好别用。

用 Python 了的情况下,性能本身就是个大坑,除非你用原生函数或者 numpy 等库减少 Python 层面的调用开销。

其次,捕获变量这件事通常与回调相关,需要捕获变量的回调,通常意味着设计问题。Java 就是一个语法不完善的语言,所以需要 lambda. JavaScript 自从有了 async/await 之后,回调一下子少了很多。如果你的代码 lambda 很多,请思考一下是不是有设计上的问题。

最后再说一下 lambda 捕获变量是有坑的,比如在 python 里面,这段代码的结果是什么?

捕获变量确实有坑,但是非匿名函数也有同样的坑。对比应该是需要控制变量法做对比:Lambda 和普通函数才是比较的对象。以上你的示例代码功能上跟这个没有区别,都是调用时才读取i的值也就是 9。同样,你前面所说的性能问题也没有公平对比,你应该对比的是嵌套函数。

l = []
for i in range (10):
    def printer ():
        print (i)
    l.append (printer)
for f in l:
    f ()

而 partial 本身通过传参,已经复制不可变的整数i,所以跟这个的语义显然是完全不同的。因此你这个例子既不能说明 lambda 好,也不能说明 lambda 坏。这只是说明内层函数对外层变量的永远读取的是最新值,是一个很有用的功能。我可以用 lambda 实现一个简易的支持单参数的 partial

def my_partial (f, x):
    return lambda *args: f (x, *args) 

l = []
for i in range (10):
    l.append (my_partial (print, i))
for f in l:
    f ()

结果是 0123456789

写 py 的最喜欢写出超级复杂的一行了
– 感谢 py 的限制

我这两天研究了一下 C#,结果打开了新世界
我写了一个简单的 CLI 菜单框架,最开始如果不用委托的话就要每一个菜单项都要单独写一个菜单项的派生类出来,而这个类里面有用的只有一个字符串和相对应的 action
有了委托就方便多了,以至于我开始滥用 lambda:

这绝对谈不上优雅,但是能用。。。。