用于生成变量和函数的 Elm 语法:如何区分?

Elm syntax for generating variables and functions: how to tell the difference?

如果这是一个愚蠢的问题,请原谅我,但我一直在研究 "Programming Elm" 有一件事让我觉得有点奇怪:在文本中他展示了一个创建记录的例子,

dog = { name = "Tucker", age = 11 }

紧接着他展示了一个 returns 一条记录

的函数
haveBirthday d = { name = d.name, age = d.age + 1 }

对我来说,两者的语法似乎非常相似。编译器如何知道哪个是哪个?函数右边的+表示变化,一定是函数?由于存在争论,d?还是生成记录和生成函数的区别非常明显,只是在这种情况下它们看起来如此相似?还是在某种微妙的方式上,我还没有掌握禅宗,它们实际上是同一件事? (也就是说,类似"everything is a function"?)

我看过 https://elm-lang.org/docs/syntax#functions -- the docs are very user-friendly, but brief. Are there any other resources that give a more didactic view of the syntax (like this book Haskell)?

感谢一路上的帮助。

在以 "side-effects" 为标准的命令式语言中,术语 "function" 通常用于描述更恰当地称为过程或子例程的内容。一组在调用时执行的指令,其中执行顺序和重新评估是必不可少的,因为变异和其他副作用可以随时随地改变任何东西。

然而,在函数式编程中,函数的概念更接近该术语的数学意义,其中其 return 值完全基于其参数计算。对于像 Elm 这样的 "pure" 函数式语言尤其如此,它通常不允许 "side-effects"。也就是说,与 "outside world" 交互而不通过输入参数或 return 值的效果。在纯函数式语言中,拥有一个不接受任何参数的函数是没有意义的,因为它总是做同样的事情,一次又一次地计算相同的值只是浪费。没有参数的函数实际上只是一个值。因此,函数定义和值绑定可以仅根据它是否有任何参数来区分。

但是也有很多混合编程语言。事实上,大多数函数式语言都是混合体,它们允许有副作用,但仍然接近函数的数学意义。这些语言通常也没有不带参数的函数,而是使用一种称为 unit() 的特殊类型,它只有一个值,也称为 unit() ,用于表示不接受 重要 输入的函数,或者 return 不重要的函数。由于 unit 只有一个值,因此它不包含任何重要信息。

许多函数式语言甚至没有接受多个参数的函数。在 Elm 和许多其他语言中,一个函数只接受一个参数。不多也不少,永远如此。您可能已经看到 看起来 有多个参数的 Elm 代码,但这只是一种错觉。或语法糖,因为它在语言理论行话中被称为。

当您看到这样的函数定义时:

add a b = a + b

实际转化为:

add = \a -> \b -> a + b

一个接受参数 a 的函数,然后 return 另一个接受参数 b 的函数,它进行实际计算并 return 得出结果.这叫做currying.

为什么要这样做?因为它使得 partially apply 功能非常方便。您可以只省略最后一个或最后几个参数,然后您将返回一个函数,而不是错误,您可以稍后完全应用它来获得结果。这使您能够做一些非常方便的事情。

我们来看一个例子。要从上面完全苹果 add 我们只需要这样做:

add 2 3

编译器实际上将其解析为 (add 2) 3,因此我们已经完成了部分应用,但随后立即应用到另一个值。但是,如果我们想将 2 添加到一大堆东西中并且不想在任何地方写 add 2 怎么办,因为 DRY 等等?我们写一个函数:

add2ToThings thing =
  add 2 thing

(好吧,也许有点做作,但请听我说)

部分应用程序使我们能够缩短这个时间!

add2ToThings =
  add 2

你知道这是怎么回事吗? add 2 return 是一个函数,我们只是给它起一个名字。在 OOP 中已经有 numerous books 写过这个绝妙的想法,但他们称之为 "dependency injection" 并且在使用 OOP 技术实现时通常会稍微冗长一些。

无论如何,假设我们有一个 "thing" 的列表,我们可以通过像这样映射它来获得一个新列表,其中 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) 

由于部分应用,管道运算符是 "discovered"。否则它就不能作为普通运算符来实现,而必须作为解析器中的特殊语法规则来实现。它的实现(本质上)只是:

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

这就是为什么函数式编程很棒。