如果这是一个愚蠢的问题,请原谅我,但我一直在经历“ 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这样的“纯”功能语言尤其如此,该语言通常不允许“副作用”。也就是说,无需通过输入参数或返回值即可与“外部世界”交互的效果。在纯函数式语言中,没有任何参数的函数是没有意义的,因为它总是会做相同的事情,而一次又一次地计算相同的值只是浪费。没有参数的函数实际上只是一个值。因此,可以仅根据函数定义和值绑定是否具有任何参数来对其进行区分。
但是也有许多混合编程语言。实际上,大多数功能语言都是混合的,可以带来副作用,但仍然与函数的数学意义相近。这些语言通常也没有没有参数的函数,而是使用一种称为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
所以这就是函数式编程很棒的原因。