如果这是一个愚蠢的问题,请原谅我,但是我一直在经历“编程榆木”问题,这让我有些奇怪:在文本中,他展示了创建唱片的示例,
dog = { name = "Tucker", age = 11 }
Run Code Online (Sandbox Code Playgroud)
然后他显示了一个返回记录的函数
haveBirthday d = { name = d.name, age = d.age + 1 }
Run Code Online (Sandbox Code Playgroud)
在我看来,两者的语法非常相似。编译器如何知道哪个是哪个?通过+对功能的右侧,即意味着改变,所以它必须是一个函数?的事实,有一个说法,d?还是生成记录和函数之间的区别非常明显,只是在这种情况下它们看起来如此相似?还是我还没有掌握禅的某种微妙的方式,实际上它们是同一回事?(就是说,“一切都是功能”?)
我看过https://elm-lang.org/docs/syntax#functions-这些文档非常人性化,但简短。是否有其他资源可以更全面地说明语法(就像本书对Haskell所做的那样)?
多谢您的协助。
在以“副作用”为常态的命令式语言中,术语“功能”通常用于描述更合适的过程或子例程。在调用时要执行的一组指令,以及执行和重新评估的顺序至关重要的位置,因为突变和其他副作用可以随时随地改变任何内容。
但是,在函数式编程中,函数的概念更接近于术语的数学含义,其中函数的返回值完全基于其参数来计算。对于像Elm这样的“纯”功能语言尤其如此,该语言通常不允许“副作用”。也就是说,无需通过输入参数或返回值即可与“外部世界”交互的效果。在纯函数式语言中,没有任何参数的函数是没有意义的,因为它总是会做相同的事情,而一次又一次地计算相同的值只是浪费。没有参数的函数实际上只是一个值。因此,可以仅根据函数定义和值绑定是否具有任何参数来对其进行区分。
但是,还有许多混合编程语言。实际上,大多数功能语言都是混合的,可以带来副作用,但仍然与函数的数学意义相近。这些语言通常也没有没有参数的函数,而是使用一种称为unit或的特殊类型(),该类型只有一个值,也称为unitor (),用于表示一个不接受任何重要输入或不返回任何重要内容的函数。由于unit只有一个值,因此它不包含任何重要信息。
许多函数式语言甚至都没有带有多个参数的函数。在Elm和许多其他语言中,函数仅接受一个参数。没有更多,也没有更少。您可能已经看到了Elm代码,其中似乎有多个参数,但这只是一种幻想。或用语言理论术语称为语法糖。
当您看到这样的函数定义时:
add a b = a + b
Run Code Online (Sandbox Code Playgroud)
实际翻译成这样:
add = \a -> \b -> a + b
Run Code Online (Sandbox Code Playgroud)
一个接受参数的函数a,然后返回另一个接受参数的函数b,该函数进行实际计算并返回结果。这称为currying。
为什么这样 因为这使得部分应用功能非常方便。您可以只保留最后一个或最后几个参数,然后返回一个函数,而不是错误,您可以稍后完全应用该函数以获取结果。这使您能够做一些非常方便的事情。
让我们看一个例子。为了完全add从上方苹果,我们只需要:
add 2 3
Run Code Online (Sandbox Code Playgroud)
编译器实际上将此解析为(add 2) 3,因此我们已经完成了部分应用程序,但随后立即将其应用于另一个值。但是,如果我们想添加2很多东西,又不想add 2因为DRY之类的东西而无处不在,该怎么办?我们编写一个函数:
add2ToThings thing =
add 2 thing
Run Code Online (Sandbox Code Playgroud)
(好吧,也许有些人为,但请留在我身边)
部分应用程序使我们可以使其更短!
add2ToThings =
add 2
Run Code Online (Sandbox Code Playgroud)
你知道那是怎么回事吗?add 2返回一个函数,我们只是给它起一个名字。关于OOP中的这一奇妙想法,已经有很多著作被写成书,但是他们称其为“依赖注入”,当用OOP技术实现时,它通常会更加冗长。
无论如何,说我们有一个“事物”列表,我们可以2通过映射到上面的内容来获得一个添加了所有内容的新列表:
List.map add2ToThings things
Run Code Online (Sandbox Code Playgroud)
但是我们可以做得更好!由于add 2实际上比我们给它命名的名称短,我们不妨直接使用它:
List.map (add 2) things
Run Code Online (Sandbox Code Playgroud)
好的,但是接下来我们要filter说出每个确切的值5。实际上,我们也可以部分地应用infix运算符,但是我们必须将运算符括在括号中,以使其表现得像普通函数:
List.filter ((/=) 5) (List.map (add 2) things)
Run Code Online (Sandbox Code Playgroud)
但是,这看起来有点令人费解,并且自从我们编写filter 之后就向后读map。幸运的是,我们可以使用Elm的管道运算符对其|>进行一些清理:
things
|> List.map (add 2)
|> List.filter ((/=) 5)
Run Code Online (Sandbox Code Playgroud)
管道操作员由于部分应用而被“发现”。没有它,就不能将其实现为普通运算符,而必须将其实现为解析器中的特殊语法规则。它的实现(基本上)只是:
x |> f = f x
Run Code Online (Sandbox Code Playgroud)
它在左侧带有一个任意参数,在右侧带有一个函数,然后将该函数应用于该参数。而且由于部分应用程序,我们可以方便地获得右侧传递的函数。
因此,在三行普通的惯用Elm代码中,我们已经使用了部分应用程序四次。如果没有这些,并且很烦人,我们将不得不编写如下内容:
List.filter (\thing -> thing /= 5) (List.map (\thing -> add 2 thing) things)
Run Code Online (Sandbox Code Playgroud)
或者我们可能想用一些变量绑定来编写它,以使其更具可读性:
let
add2ToThings thing =
add 2 thing
thingsWith2Added =
List.map add2ToThings things
thingsWith2AddedAndWithout5 =
List.filter (\thing -> thing /= 5) thingWith2Added
in
thingsWith2AddedAndWithout5
Run Code Online (Sandbox Code Playgroud)
所以这就是函数式编程很棒的原因。