看来又是个 ALGOL-like 语言学残的……补习一下历史罢。
世界上本来就没什么正经的函数(function) ,先是在 1930 年代 A.Church 提出 λ 演算里用于替换绑定变量为其它表达式的 λ 抽象以及把 λ 抽象绑定到一个名称作为变量的用法,后是 1950 年代 J.McCarthy 明确用这样的方法,来实现的一般意义上的所谓的函数。这样,λ 抽象实质上就是所谓的匿名函数(anonymous) 。(在原始的 λ 演算中,这些匿名函数都是所谓的纯函数(pure function) ,之后有另外的扩展,但这个意义上正经地搞是在 1970 年代以后的事。)
之后(从 1950 年代末开始),一坨不上道的 ALGOL-like 语言把函数弱化成过程(procedure) 乃至子例程(subroutine) 偷换概念,让函数总是总是具有所谓的函数名,先接触这类语言入门的用户就被念歪经的设计整残了;如 Java 之流的更叒鸡的语言把过程继续阉割,去掉了所谓的自由函数(free function) ,叠床架屋依附于所谓的对象(原本对象就跟类的实例无关,考虑到 Java 照抄某些语言的历史,这里是牵强附会)之上,搞出什么方法(method) ,数典忘祖;于是用这样的语言入门的用户更加不会正常的思维方式了。现在一坨语言重新引入 lambda 表达式来表达 λ 抽象,说到底不过是矫治习惯不正常的理解困难的设计的症状,要求端正视听罢了——只不过很遗憾,因为兼容和历史包袱,已有的半残的“函数”或者更残废的“方法”的语法还是被保留,以至于理解起来更容易迷糊和浪费时间。
(至于在子例程以外的函数的其它实现,如 coroutine 和 continuation 也是很早的支线,比 Java 资格老大概 30 年,这里按下不表,等 Java 之流的语言先赶上再说。)
当然,不够正经的、歧义满天飞的所谓函数数学早已有之,历史上翻来覆去倒腾了几百年也没个谱,后来才发现严格意义上可以是一回事(例如,以所谓可计算函数(computable function) 作为模型):
https://en.wikipedia.org/wiki/History_of_the_function_concept和这样的模型比较接近的是一般大学高等数学会教你的基于 Cartesian product 模型化的映射(map) 。数学中更常见的所谓的“函数”和泛函(functional) 都是其缩水版,要求值域是实数,这在日常应用中往往是多余的限制;高中用的“函数”的 Dirchlet 定义,更是连定义域都锁死了,也不讲上域(codomain) ;除了数理逻辑讨论的一些概念,对语法和语义的分割也聊胜于无。所以对理解关心如何用代码的描述和解决计算问题的角度来说,用这些不够严格的传统数学概念,大多讲了白讲。
现代编程语言常规支持表达的某一类偏可计算函数(partial computable function),除了不可解度(不超越图灵完备)以外,比映射能实现的计算作用(computational effect) 更强,例如允许副作用(side effect) ,也不要求是全函数(totality) 保证确定性终止。(倒是某些 FP 语言会别有用心地妄想把其中某几个特性干掉给阉回去。) Java 之流虽然叒鸡,但是比起来还算是比较正经的了。