【问题标题】:Elm syntax for generating variables and functions: how to tell the difference?用于生成变量和函数的 Elm 语法:如何区分?
【发布时间】:2019-11-10 19:09:01
【问题描述】:

如果这是一个愚蠢的问题,请原谅我,但我一直在阅读“编程榆树”,有一件事情让我觉得有点奇怪:在文本中他展示了一个创建记录的示例,

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 的作用)?

感谢您一路上的帮助。

【问题讨论】:

  • “事实上有争论,d?” - 就是这个。
  • The Elm Guide 是主要的学习资源。
  • 我很好奇你认为函数是什么,作为一个概念。您是否以命令式的方式思考,其中函数只是延迟计算,可能有也可能没有参数?
  • 我错过了要求书籍推荐的问题部分。 (这对于 SO 来说完全是题外话,但我会放纵。)我知道有 this,它非常详细、彻底且进展缓慢 - 但是它是为 elm 的旧版本编写的,而不是当前版本(0.19),因此本书中的所有代码示例都不再有效。
  • @glennsl -- 我想这可能是一个定义。也许是一组稍后要执行的指令?或者我可能会说,在阅读了一些 FP 文献之后,我会将函数视为从输入到输出的映射。我已经编程了足够长的时间(当然不是在 Elm 中),我只是通过使用它“知道”它是什么。

标签: elm


【解决方案1】:

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

然而,在函数式编程中,函数的概念更接近该术语的数学意义,其返回值完全基于其参数计算。对于像 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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    • 2016-05-16
    • 2016-07-04
    • 1970-01-01
    • 2017-07-12
    相关资源
    最近更新 更多