核心语言
我们首先来感受一下 Elm 代码!
我们的目标是熟悉值和函数,以便在稍后学习较大的示例时更自信地阅读 Elm 代码。
值
Elm 中最小的构建模块称为值。其中包括诸如 42
、True
和 "Hello!"
之类的值。
我们先从观察数字开始
[ { "input": "1 + 1", "value": "\u001b[95m2\u001b[0m", "type_": "number" } ]
此页面上的所有示例都是交互式的,因此单击此黑框 ⬆️,光标应当开始闪烁。输入 2 + 2
,然后按 Enter 键。它应打印出 4
。你应该能够以同样的方式与此页面上的所有示例进行交互!
尝试输入诸如 30 * 60 * 1000
和 2 ^ 4
等内容。它应该像计算器一样工作!
进行数学运算很好,但令人惊讶的是,在大多数程序中这并不常见!使用字符串进行操作更加常见,比如这样
[ { "input": "\"hello\"", "value": "\u001b[93m\"hello\"\u001b[0m", "type_": "String" }, { "input": "\"butter\" ++ \"fly\"", "value": "\u001b[93m\"butterfly\"\u001b[0m", "type_": "String" } ]
尝试使用 (++)
运算符 ⬆️,将一些字符串组合在一起
当我们开始编写函数对其进行转换时,这些基本值才会变得更有趣!
注意:了解更多有关 (+)
、(/)
和 (++)
等运算符,请参阅 Basics
模块的文档。值得通读该软件包中的所有文档!
函数
函数是一种转换值的方法。输入一个值,生成另一个值。
例如,这里有一个 greet
函数,它输入一个名字并打招呼
[ { "add-decl": "greet", "input": "greet name =\n \"你好 \" ++ name ++ \"!\"\n", "value": "\u001b[36m<函数>\u001b[0m", "type_": "字符串 -> 字符串" }, { "input": "问候语 \"Alice\"", "value": "\u001b[93m\"你好 Alice!\"\u001b[0m", "type_": "字符串" }, { "input": "greet \"Bob\"", "value": "\u001b[93m\"Hello Bob!\"\u001b[0m", "type_": "字符串" } ]
尝试问候别人,如 "Stokely"
或 "Kwame"
⬆️
传递给函数的值通常称为参数,因此你可以说“greet
是一个带有单个参数的函数”。
好的,现在要解决问候语的问题了,那么一个带两个参数的 madlib
函数如何?
[ { "add-decl": "madlib", "input": "madlib animal adjective =\n \"高调的 \" ++ animal ++ \" 穿着 \" ++ adjective ++ \" 的短裤。\"\n", "value": "\u001b[36m<函数>\u001b[0m", "type_": "字符串 -> 字符串 -> 字符串" }, { "input": "madlib \"cat\" \"ergonomic\"", "value": "\u001b[93m\"高调的猫穿着符合人体工学的短裤。\"\u001b[0m", "type_": "字符串" }, { "input": "madlib (\"butter\" ++ \"fly\") \"metallic\"", "value": "\u001b[93m\"高调的蝴蝶穿着金属短裤。\"\u001b[0m", "type_": "字符串" } ]
尝试给 madlib
函数添加两个参数 ⬆️
注意在第二个示例中,我们如何使用括号将 "butter" ++ "fly"
组合在一起。每个参数需要是原始值,例如 "cat"
,或者需要用括号括起来!
注意:来自 JavaScript 等语言的人可能会惊讶,因为这里的函数看起来不同
madlib "cat" "ergonomic" -- Elm
madlib("cat", "ergonomic") // JavaScript
madlib ("butter" ++ "fly") "metallic" -- Elm
madlib("butter" + "fly", "metallic") // JavaScript
这乍一看可能令人惊讶,但这种风格最终使用了更少的括号和逗号。一旦你习惯了它,它会让语言感觉非常干净和简约!
If 表达式
当你想在 Elm 中有条件行为时,可以使用 if 表达式。
让我们创建一个新的 greet
函数,以便对亚伯拉罕·林肯总统表示适当的尊重
[ { "add-decl": "greet", "input": "greet name =\n if name == \"Abraham Lincoln\" then\n \"Greetings Mr. President!\"\n else\n \"Hey!\"\n", "value": "\u001b[36m<函数>\u001b[0m", "type_": "字符串 -> 字符串" }, { "input": "greet \"Tom\"", "value": "\u001b[93m\"Hey!\"\u001b[0m", "type_": "字符串" }, { "input": "greet \"Abraham Lincoln\"", "value": "\u001b[93m\"Greetings Mr. President!\"\u001b[0m", "type_": "字符串" } ]
可能还有其他需要涵盖的情况,但暂时这样做就足够了!
列表
列表是 Elm 中最常见的数据结构之一。它们包含一系列相关的事物,类似于 JavaScript 中的数组。
列表可以容纳多个值。这些值都必须具有相同的类型。以下是一些使用来自 List
模块的函数的示例
[ { "add-decl": "names", "input": "names =\n [ \"Alice\", \"Bob\", \"Chuck\" ]\n", "value": "[\u001b[93m\"Alice\"\u001b[0m,\u001b[93m\"Bob\"\u001b[0m,\u001b[93m\"Chuck\"\u001b[0m]", "type_": "列表 字符串" }, { "input": "List.isEmpty names", "value": "\u001b[96m否\u001b[0m", "type_": "布尔值" }, { "input": "List.length names", "value": "\u001b[95m3\u001b[0m", "type_": "字符串" }, { "input": "List.reverse names", "value": "[\u001b[93m\"Chuck\"\u001b[0m,\u001b[93m\"Bob\"\u001b[0m,\u001b[93m\"Alice\"\u001b[0m]", "type_": "列表 字符串" }, { "add-decl": "numbers", "input": "numbers =\n [4,3,2,1]\n", "value": "[\u001b[95m4\u001b[0m,\u001b[95m3\u001b[0m,\u001b[95m2\u001b[0m,\u001b[95m1\u001b[0m]", "type_": "列表 数字" }, { "input": "List.sort numbers", "value": "[\u001b[95m1\u001b[0m,\u001b[95m2\u001b[0m,\u001b[95m3\u001b[0m,\u001b[95m4\u001b[0m]", "type_": "列表 数字" }, { "add-decl": "increment", "input": "increment n =\n n + 1\n", "value": "\u001b[36m<函数>\u001b[0m", "type_": "数字 -> 数字" }, { "input": "List.map increment numbers", "value": "[\u001b[95m5\u001b[0m,\u001b[95m4\u001b[0m,\u001b[95m3\u001b[0m,\u001b[95m2\u001b[0m]", "type_": "列表 数字" } ]
试着创建自己的列表并使用函数如 List.length
⬆️
请记住,列表中的所有元素必须为同一类型!
元组
元组是另一种有用的数据结构。元组可以包含两个或三个值,且每个值可以为任意类型。常见用途是使用函数返回多个值。以下函数获取一个名称并将消息提供给用户
[ { "add-decl": "isGoodName", "input": "isGoodName name =\n if String.length name <= 20 then\n (True, \"name accepted!\")\n else\n (False, \"name was too long; please limit it to 20 characters\")\n", "value": "\u001b[36m<函数>\u001b[0m", "type_": "字符串 -> ( 布尔值, 字符串 )" }, { "input": "isGoodName \"Tom\"", "value": "(\u001b[96mTrue\u001b[0m, \u001b[93m\"name accepted!\"\u001b[0m)", "type_": "( 布尔值, 字符串 )" } ]
这会非常方便,但当事情变得复杂时,通常最好使用记录代替元组。
记录
记录可以包含许多值,且每个值均与一个名称关联。
以下是代表英国经济学家 John A. Hobson 的记录
[ { "add-decl": "john", "input": "john =\n { first = \"John\"\n , last = \"Hobson\"\n , age = 81\n }\n", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m81\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Hobson\"\u001b[0m }", "type_": "{ age : 数字,first : 字符串,last : 字符串 }" }, { "input": "john.last", "value": "\u001b[93m\"Hobson\"\u001b[0m", "type_": "字符串" } ]
我们定义了一个包含三个域的记录,其中包含有关 John 的姓名和年龄的信息。
试着访问其他域,如 john.age
⬆️
您还可以通过以下方式使用“域访问函数”来访问记录域
[ { "add-decl": "john", "input": "john = { first = \"John\", last = \"Hobson\", age = 81 }", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m81\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Hobson\"\u001b[0m }", "type_": "{ age : number, first : String, last : String }" }, { "input": ".last john", "value": "\u001b[93m\"Hobson\"\u001b[0m", "type_": "String" }, { "input": "List.map .last [john,john,john]", "value": "[\u001b[93m\"Hobson\"\u001b[0m,\u001b[93m\"Hobson\"\u001b[0m,\u001b[93m\"Hobson\"\u001b[0m]", "type_": "List String" } ]
更新记录中的值通常是有用的
[ { "add-decl": "john", "input": "john = { first = \"John\", last = \"Hobson\", age = 81 }", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m81\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Hobson\"\u001b[0m }", "type_": "{ age : number, first : String, last : String }" }, { "input": "{ john | last = \"Adams\" }", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m81\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Adams\"\u001b[0m }", "type_": "{ age : number, first : String, last : String }" }, { "input": "{ john | age = 22 }", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m22\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Hobson\"\u001b[0m }", "type_": "{ age : number, first : String, last : String }" } ]
如果你想大声说出这些表达式,你可以说类似于“我想要一个新的 John,他的姓是 Adams”或“一个年龄为 22 岁的人”。
请注意,当我们更新 `john` 的一些字段时,我们会创建一个全新的记录。它不会覆盖现有的记录。Elm 通过尽可能多地共享内容来实现这种高效。如果你更新十个字段中的一个,则新记录会共享九个未更改的值。
因此,一个更新年龄的函数可能如下所示
[ { "add-decl": "celebrateBirthday", "input": "celebrateBirthday person =\n { person | age = person.age + 1 }\n", "value": "\u001b[36m<function>\u001b[0m", "type_": "{ a | age : number } -> { a | age : number }" }, { "add-decl": "john", "input": "john = { first = \"John\", last = \"Hobson\", age = 81 }", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m81\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Hobson\"\u001b[0m }", "type_": "{ age : number, first : String, last : String }" }, { "input": "celebrateBirthday john", "value": "{ \u001b[37mage\u001b[0m = \u001b[95m82\u001b[0m, \u001b[37mfirst\u001b[0m = \u001b[93m\"John\"\u001b[0m, \u001b[37mlast\u001b[0m = \u001b[93m\"Hobson\"\u001b[0m }", "type_": "{ age : number, first : String, last : String }" } ]
这样更新记录字段真的很常见,因此我们在下一部分中会看到更多内容!