elm - 用于生成变量和函数的 Elm 语法:如何区分?
问题描述
如果这是一个愚蠢的问题,请原谅我,但我一直在阅读“编程榆树”,有一件事情让我觉得有点奇怪:在文本中他展示了一个创建记录的示例,
dog = { name = "Tucker", age = 11 }
然后他展示了一个返回记录的函数
haveBirthday d = { name = d.name, age = d.age + 1 }
对我来说,两者的语法似乎非常相似。编译器如何知道哪个是哪个?在+
函数的右侧,这意味着变化,所以它必须是一个函数?事实上,有一个论点,d
?还是生成记录和生成函数的区别很明显,只是在这种情况下,它们看起来如此相似?还是以某种我还没有掌握禅的微妙方式,它们实际上是同一回事?(也就是说,“一切都是函数”之类的东西?)
我查看了https://elm-lang.org/docs/syntax#functions - 文档非常用户友好,但简短。是否有任何其他资源可以对语法提供更具指导性的观点(就像这本书对 Haskell 所做的那样)?
感谢您一路上的任何帮助。
解决方案
在以“副作用”为规范的命令式语言中,术语“函数”通常用于描述更恰当地称为过程或子例程的内容。调用时要执行的一组指令,其中执行和重新评估的顺序是必不可少的,因为突变和其他副作用可以随时随地改变任何东西。
然而,在函数式编程中,函数的概念更接近该术语的数学意义,其中它的返回值完全基于其参数计算。对于像 Elm 这样的“纯”函数式语言尤其如此,它通常不允许“副作用”。也就是说,无需通过输入参数或返回值即可与“外部世界”交互的效果。在纯函数式语言中,没有任何参数的函数是没有意义的,因为它总是做同样的事情,一次又一次地计算相同的值只是浪费。没有参数的函数实际上只是一个值。因此,函数定义和值绑定可以仅根据它是否有任何参数来区分。
但也有许多混合编程语言。事实上,大多数函数式语言都是混合的,它们允许副作用但仍然接近函数的数学意义。这些语言通常也没有没有参数的函数,而是使用一种特殊的类型,称为unit
or ()
,它只有一个值,也称为unit
or ()
,用于表示不接受重要输入或不返回任何重要内容的函数。由于unit
只有一个值,它不包含重要信息。
许多函数式语言甚至没有接受多个参数的函数。在 Elm 和许多其他语言中,函数只接受一个参数。没有更多也没有更少,永远。您可能已经看到似乎有多个参数的 Elm 代码,但这完全是一种错觉。或语法糖,因为它在语言理论术语中被称为。
当你看到这样的函数定义时:
add a b = a + b
实际转化为:
add = \a -> \b -> a + b
一个接受参数的函数a
,然后返回另一个接受参数的函数b
,该函数进行实际计算并返回结果。这称为柯里化。
为什么要这样做?因为它使得部分应用函数非常方便。你可以省略最后一个或最后几个参数,然后你得到一个函数,而不是一个错误,你可以稍后完全应用它来获得结果。这使您可以做一些非常方便的事情。
让我们看一个例子。要从上面完全苹果化add
,我们只需这样做:
add 2 3
编译器实际上将其解析为(add 2) 3
,因此我们已经完成了部分应用,但随后立即应用于另一个值。但是,如果我们想添加2
一大堆东西并且不想add 2
到处写,因为 DRY 之类的怎么办?我们写一个函数:
add2ToThings thing =
add 2 thing
(好吧,也许有点做作,但请留在我身边)
部分应用程序使我们可以缩短它!
add2ToThings =
add 2
你明白它是如何工作的吗?add 2
返回一个函数,我们只是给它一个名字。在 OOP中已经有很多关于这个奇妙想法的书籍,但他们称之为“依赖注入”,并且在使用 OOP 技术实现时通常会稍微冗长一些。
无论如何,假设我们有一个“事物”列表,我们可以通过像这样映射它来获得一个2
添加到所有内容的新列表:
List.map add2ToThings things
但我们可以做得更好!由于add 2
实际上比我们给它的名字要短,我们不妨直接使用它:
List.map (add 2) things
好的,但是然后说我们想要filter
精确地输出每个值5
。实际上,我们也可以部分应用中缀运算符,但我们必须将运算符括在括号中以使其表现得像一个普通函数:
List.filter ((/=) 5) (List.map (add 2) things)
不过,这开始看起来有点令人费解,并且从 we之后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
这就是为什么函数式编程很棒的原因。
推荐阅读
- c++ - 如何使用指针将数组结构发送到函数中
- react-native - 尽管输入了数据,但仍会触发 Formik 验证错误
- api - 如何将带有动态表格的网页抓取到 Google 表格中
- c++ - Openssl C/C++ 以编程方式从模数和指数中获取 RSA 公钥
- reactjs - React Native axios api 调用 Shopify
- javascript - 另一个可折叠部分中的可折叠部分不会足够展开
- python - Xpath如何检查给定父节点是否存在子元素?
- bazel - 如何在 Bazel 的 WORKSPACE 中设置 ENV VAR
- elasticsearch - Elasticsearch 中的“exists”过滤器是否需要索引
- excel - 在 Google 表格中编写动态引用(如 Excel 中的“#”)