在以“副作用”为规范的命令式语言中,术语“函数”通常用于描述更恰当地称为过程或子例程的内容。调用时要执行的一组指令,其中执行和重新评估的顺序是必不可少的,因为突变和其他副作用可以随时随地改变任何东西。
然而,在函数式编程中,函数的概念更接近该术语的数学意义,其返回值完全基于其参数计算。对于像 Elm 这样的“纯”函数式语言尤其如此,它通常不允许“副作用”。也就是说,无需通过输入参数或返回值即可与“外部世界”交互的效果。在纯函数式语言中,没有任何参数的函数是没有意义的,因为它总是做同样的事情,一次又一次地计算相同的值只是浪费。没有参数的函数实际上只是一个值。因此,函数定义和值绑定可以仅根据它是否有任何参数来区分。
但也有许多混合编程语言。事实上,大多数函数式语言都是混合的,它们允许副作用但仍然接近函数的数学意义。这些语言通常也没有没有参数的函数,而是使用称为unit 或() 的特殊类型,它只有一个值,也称为unit 或(),用于表示函数不接受任何重要输入,或者不返回任何重要内容。由于unit只有一个值,所以它不包含重要信息。
许多函数式语言甚至没有接受多个参数的函数。在 Elm 和许多其他语言中,函数只接受一个参数。没有更多也没有更少,永远。您可能已经看到 似乎 有多个参数的 Elm 代码,但这完全是一种错觉。或者用语言理论术语称之为语法糖。
当你看到这样的函数定义时:
add a b = a + b
实际转化为:
add = \a -> \b -> a + b
一个接受参数a的函数,然后返回另一个接受参数b的函数,该函数进行实际计算并返回结果。这称为currying。
为什么要这样做?因为它使得partially apply 函数非常方便。你可以省略最后一个或最后几个参数,然后你得到一个函数,而不是一个错误,你可以稍后完全应用它来获得结果。这使您可以做一些非常方便的事情。
让我们看一个例子。要从上面完全苹果add,我们只需这样做:
add 2 3
编译器实际上将其解析为(add 2) 3,因此我们已经完成了部分应用,但随后立即应用于另一个值。但是,如果我们想将2 添加到一大堆东西中并且不想到处写add 2,因为DRY 之类的怎么办?我们写一个函数:
add2ToThings thing =
add 2 thing
(好吧,也许有点做作,但请留在我身边)
部分应用让我们可以缩短这个时间!
add2ToThings =
add 2
你明白它是如何工作的吗? add 2 返回一个函数,我们只是给它一个名字。在 OOP 中已经有 numerous books 写过这个奇妙的想法,但他们称之为“依赖注入”,并且在使用 OOP 技术实现时通常会稍微冗长一些。
无论如何,假设我们有一个“事物”列表,我们可以通过像这样映射它来获得一个将2 添加到所有内容的新列表:
List.map add2ToThings things
但我们可以做得更好!由于add 2实际上比我们给它的名字要短,我们还不如直接使用它:
List.map (add 2) things
好的,然后说我们想filter 取出每个正好是5 的值。我们实际上也可以部分应用中缀运算符,但我们必须将运算符括在括号中,使其表现得像一个普通函数:
List.filter ((/=) 5) (List.map (add 2) things)
虽然这开始看起来有点令人费解,并且自从我们filter 在 我们map 之后向后读取。幸运的是,我们可以使用 Elm 的管道运算符 |> 对其进行清理:
things
|> List.map (add 2)
|> List.filter ((/=) 5)
管道运算符由于部分应用而被“发现”。没有它,它就不能作为一个普通的运算符来实现,而必须在解析器中作为一个特殊的语法规则来实现。它的实现(基本上)只是:
x |> f = f x
它的左侧有一个任意参数,右侧有一个函数,然后将函数应用于参数。并且由于部分应用,我们可以方便地在右侧获取一个函数。
因此,在三行普通的 Elm 惯用代码中,我们使用了四次偏应用。没有它和柯里化,我们就不得不写这样的东西:
List.filter (\thing -> thing /= 5) (List.map (\thing -> add 2 thing) things)
或者我们可能想用一些变量绑定来编写它以使其更具可读性:
let
add2ToThings thing =
add 2 thing
thingsWith2Added =
List.map add2ToThings things
thingsWith2AddedAndWithout5 =
List.filter (\thing -> thing /= 5) thingWith2Added
in
thingsWith2AddedAndWithout5
这就是函数式编程很棒的原因。