这是非常令人兴奋的一章, 通过这一章可以学习到很多haskell中重要的概念, 让你的haskell代码变得更优雅!
匿名函数
匿名函数就算在非函数式编程语言中, 也是很重要的特性。
很多场景下, 有些函数根本不需要复用, 有些函数太短太基础不适合复用; 总之, 每一个函数都需要我们命名真的是很烦, 也让代码变得很shit, 尤其是那些我们确定只会用到一次的函数。
这时候匿名函数就来了, 对于haskell这样的, 函数作为一等公民的编程语言, 匿名函数的支持是自然而然的。
haskell的匿名函数定义方式为\arg1 arg2 ... -> funcBody
, 比如
-- 平方
\x -> x * x
-- 求和
\x y -> x + y
为什么用\
开头, 这就不提到**
Lambda 演算(Lambda Calculus)是一种形式化系统,用于研究函数定义、函数应用和递归。它由阿隆佐·邱奇(Alonzo Church)在 1930 年代发明,是计算机科学和数学中的一个重要概念。Lambda 演算是函数式编程语言(如 Haskell、Lisp 等)的理论基础。
具体可见[lambda演算系列blog](../转载|收藏/Lambda Calculus/README.md)
在本文的后面会看到, haskell就是建立在
演算的基础上的, haskell和 演算一样, 每个函数都只有一个参数, 默认柯里化, 默认部分应用。
\
这个字符和
运算符函数
对于简单的使用运算符的匿名函数, 如
\x -> x > 100
可以直接简化为
> 100
这其实是用到了函数的部分应用特性, 所以在这里只简单提一下, 后面细讲原理。
函数组合
在函数式编程中, 我们经常使用到管道的编程风格, 也就是将任务分为若干个步骤来解决, 形成了若干个函数, 然后按顺序调用函数, 上一次函数执行的返回值就是下一个函数的入参。
doTask x = f (g (h x))
haskell为这种风格提供了语法糖!
我们可以把上述代码改造为如下:
doTask x = (f . g . h) x
使用.
可以组合多个函数, 形成一个新的函数; 函数调用时, 数据将从右向左流动, 就像一条管道。
柯里化和部分应用
在
\x -> body
那么如何用单参数函数模拟一个具有多个参数的函数呢, 毕竟多参数函数才符合人类的思维习惯。
很简单!让一个单参数函数返回一个单参数函数即可。
以下两种形式其实在haskell中是等价的。
-- 单参数形式
\x -> (\y -> x + y)
-- 多参数形式
\x y -> x + y
一个多参数函数的调用过程, 其实是在循环的: 调用单参数函数, 返回一个新的单参数函数, 然后把它应用下一个参数。
把多参数函数变成一个单参数函数的过程, 就叫做柯里化(Currying)。
Currying这个词来源于逻辑学家 Haskell Curry
事实上, haskell中所有函数都只有一个参数。
此时再回想haskell的函数类型定义语法:
func :: Int -> Int -> Int
之前你肯定会疑惑, 为什么函数的参数和返回值没有进行直接区分, 而是约定最后一个类型是返回值, 前面剩余的是参数。
现在我们知道了, 原来haskell中的函数类型语法始终只有:
funcName :: argType -> returnType
func :: Int -> Int -> Int
其实是
func :: Int -> (Int -> Int)
func接受一个Int类型的参数, 返回一个Int -> Int
类型的函数。
验证一下:
ghci> :{
ghci| sum :: Int -> Int -> Int
ghci| sum x y = x + y * 2
ghci| :}
ghci> :type sum
sum :: Int -> Int -> Int
ghci> :type sum 1
sum 1 :: Int -> Int
正如预料, sum 实际只接受一个参数, 返回一个函数。当sum接受两个参数时, 实际真的只是把返回的函数应用在了第二个参数上。
由此我们了解到了下一个知识: 函数的部分应用。
部分应用
部分应用是指, 我们可以把一个多参函数只应用一部分参数, 并且得到一个包含剩余参数的函数。
由于haskell中的函数是已柯里化的(curried), 所以函数的部分应用非常容易, 在上面我们已经看到了。
中缀运算符的部分应用
众所周知, haskell中的一切均为函数, 包括运算符。
但运算符为了符合人类习惯, 可以中缀调用, 也就是两个参数分别位于一前一后。
100 > 1
对于中缀函数, 可以使用()
包括使其变为前缀函数
ghci> 100 > 1
True
ghci> (>) 100 1
True
ghci> True || False
True
ghci> (||) True False
True
对中缀函数使用部分应用时, 你可以选择应用哪个参数, 而不是像前缀函数一样需要按照参数顺序。
ghci> filter (> 3) [1, 2, 3, 4, 5]
[4,5]
ghci> filter (3 >) [1, 2, 3, 4, 5]
[1,2]
point-free风格
假设一个函数, 需要对数组中所有大于5的元素求和。
也许可以写成这样:
import Data.List (sum)
foo :: [Int] -> Int
foo xs = sum (filter (> 5) xs)
但更通常的写法是这样:
import Data.List (sum)
foo :: [Int] -> Int
foo = sum . filter (> 5)
第一种写法是在定义函数要拿参数做什么, 而第二种是在定义函数是什么, 这被称为point-free
风格。
Point-Free 风格是一种通过组合函数而不显式提及参数来定义函数的编程风格。它可以使代码更简洁和声明性,但在使用时需要权衡简洁性和可读性。