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

问题描述 投票:1回答:1

如果这是一个愚蠢的问题,请原谅我,但我一直在经历“ Programming Elm”,有一件事让我感到有些奇怪:在文本中,他显示了创建记录的示例,

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

然后紧接着他显示了一个返回记录的函数

haveBirthday d = { name = d.name, age = d.age + 1 }

对我来说,两者的语法似乎非常相似。编译器如何知道哪个是哪个?通过函数右侧的+,这意味着更改,因此它必须是函数吗?由于有一个论点,d?还是生成记录和函数之间的区别非常明显,只是在这种情况下它们看起来如此相似?还是我还没有掌握禅的某种微妙的方式,实际上它们是同一回事? (也就是说,诸如“一切都是功能”之类的东西?)

我看过https://elm-lang.org/docs/syntax#functions-这些文档非常人性化,但简短。是否还有其他资源可以更全面地说明语法(例如this book对Haskell的影响)?

感谢您在此过程中的任何帮助。

elm
1个回答
4
投票

在以“副作用”为标准的命令式语言中,术语“函数”通常用于描述更恰当的过程或子例程。一组在调用时要执行的指令,执行和重新评估的顺序至关重要,因为突变和其他副作用可以随时随地改变任何内容。

然而,在函数式编程中,函数的概念更接近于该术语的数学含义,该函数的返回值完全基于其参数来计算。对于像Elm这样的“纯”功能语言尤其如此,该语言通常不允许“副作用”。也就是说,无需通过输入参数或返回值即可与“外部世界”交互的效果。在纯函数式语言中,没有任何参数的函数是没有意义的,因为它总是会做相同的事情,而一次又一次地计算相同的值只是浪费。没有参数的函数实际上只是一个值。因此,可以仅根据函数定义和值绑定是否具有任何参数来对其进行区分。

但是也有许多混合编程语言。实际上,大多数功能语言都是混合的,可以带来副作用,但仍然与函数的数学意义相近。这些语言通常也没有没有参数的函数,而是使用一种称为unit()的特殊类型,该类型只有一个值,也称为unit(),用于表示一个函数不需要significant输入,或者不返回任何有效值。由于unit只有一个值,因此它不包含任何重要信息。

许多功能语言甚至都没有带有多个参数的功能。在Elm和许多其他语言中,函数仅接受一个参数。没有更多,也没有更少。您可能已经看到了Elm代码,该代码appears具有多个参数,但这只是一种幻想。或用语言理论术语称为语法糖。

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

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返回一个函数,我们给它起一个名字。 numerous books曾在OOP中写过关于这一奇妙想法的文章,但他们称其为“依赖注入”,使用OOP技术实现时,它通常更为冗长。

无论如何,说我们有一个“物”的列表,我们可以这样映射得到一个新列表,其中将2添加到所有内容上:

List.map add2ToThings things

但是我们可以做得更好!由于add 2实际上比我们给它的名称短,我们不妨直接使用它:

List.map (add 2) things

[确定,但接下来我们要filter剔除正好是5的每个值。实际上,我们也可以部分地使用infix运算符,但是必须将运算符括在括号中,以使其表现得像普通函数:

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

所以这就是函数式编程很棒的原因。

© www.soinside.com 2019 - 2024. All rights reserved.