言語の基礎
まずは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"
}
]
(++)
演算子を使って、いくつかの文字列を組み合わせて出力してみてください ⬆️
数値や文字列などのプリミティブな値は、値を変換する関数を作り始めるとさらに面白くなります!
Note: Basics
モジュールのドキュメントを参照することで、(+)
、(/)
、(++)
などの演算子についてより詳しく知ることができます。どこかのタイミングで、Basicsモジュールのすべてのドキュメントに目を通しておくと良いでしょう!
関数
関数は値を変換するための手段です。ある値を取り込み、別の値を生成します。
たとえば、名前を取り込んで挨拶をするgreet
関数は次の通りです。
[
{
"add-decl": "greet",
"input": "greet name =\n \"Hello \" ++ name ++ \"!\"\n",
"value": "\u001b[36m<function>\u001b[0m",
"type_": "String -> String"
},
{
"input": "greet \"Alice\"",
"value": "\u001b[93m\"Hello Alice!\"\u001b[0m",
"type_": "String"
},
{
"input": "greet \"Bob\"",
"value": "\u001b[93m\"Hello Bob!\"\u001b[0m",
"type_": "String"
}
]
"Stokely"
や"Kwame"
など、他の誰かに挨拶してみてください⬆️
関数に渡された値は一般に引数と呼ばれます。「greet
は1つの引数を取る関数である」と言えます。
さて、これで挨拶はできましたが、2つの引数を取るmadlib
関数はどうでしょうか?
[
{
"add-decl": "madlib",
"input": "madlib animal adjective =\n \"The ostentatious \" ++ animal ++ \" wears \" ++ adjective ++ \" shorts.\"\n",
"value": "\u001b[36m<function>\u001b[0m",
"type_": "String -> String -> String"
},
{
"input": "madlib \"cat\" \"ergonomic\"",
"value": "\u001b[93m\"The ostentatious cat wears ergonomic shorts.\"\u001b[0m",
"type_": "String"
},
{
"input": "madlib (\"butter\" ++ \"fly\") \"metallic\"",
"value": "\u001b[93m\"The ostentatious butterfly wears metallic shorts.\"\u001b[0m",
"type_": "String"
}
]
madlib
関数に2つの引数を渡してみてください⬆️
2つ目の例では、括弧を使って"butter" ++ "fly"
を一緒のグループにする方法に注目してください。各引数は"cat"
のようなプリミティブな値であるか、括弧で囲む必要があります!
Note: JavaScriptなどの言語を使用している方々は、次の例のように関数が違って見えることに驚くかもしれません。
madlib "cat" "ergonomic" -- Elm
madlib("cat", "ergonomic") // JavaScript
madlib ("butter" ++ "fly") "metallic" -- Elm
madlib("butter" + "fly", "metallic") // JavaScript
はじめは驚くかもしれませんが、Elmのスタイルは括弧やカンマをあまり使わずに済みます。慣れるとElm言語が本当にきれいで最小限なものに感じられます!
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<function>\u001b[0m",
"type_": "String -> String"
},
{
"input": "greet \"Tom\"",
"value": "\u001b[93m\"Hey!\"\u001b[0m",
"type_": "String"
},
{
"input": "greet \"Abraham Lincoln\"",
"value": "\u001b[93m\"Greetings Mr. President!\"\u001b[0m",
"type_": "String"
}
]
他にも解説すべき例があるかもしれませんが、今はこれくらいで十分です!
リスト
リストは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_": "List String"
},
{
"input": "List.isEmpty names",
"value": "\u001b[96mFalse\u001b[0m",
"type_": "Bool"
},
{
"input": "List.length names",
"value": "\u001b[95m3\u001b[0m",
"type_": "String"
},
{
"input": "List.reverse names",
"value": "[\u001b[93m\"Chuck\"\u001b[0m,\u001b[93m\"Bob\"\u001b[0m,\u001b[93m\"Alice\"\u001b[0m]",
"type_": "List String"
},
{
"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_": "List number"
},
{
"input": "List.sort numbers",
"value": "[\u001b[95m1\u001b[0m,\u001b[95m2\u001b[0m,\u001b[95m3\u001b[0m,\u001b[95m4\u001b[0m]",
"type_": "List number"
},
{
"add-decl": "increment",
"input": "increment n =\n n + 1\n",
"value": "\u001b[36m<function>\u001b[0m",
"type_": "number -> number"
},
{
"input": "List.map increment numbers",
"value": "[\u001b[95m5\u001b[0m,\u001b[95m4\u001b[0m,\u001b[95m3\u001b[0m,\u001b[95m2\u001b[0m]",
"type_": "List number"
}
]
独自のリストを作成してList.length
などの関数を使ってみてください⬆️
繰り返しになりますが、リストのすべての要素は同じ型でなくてはいけません!
タプル
タプルはリストとはまた異なった便利なデータ構造です。タプルは2~3個の値を保持することができ、それらの値の型はそれぞれ別々にすることができます。典型的な使いかたとしては、関数からふたつ以上の値を返す必要があるときです。次の関数は名前を受け取り、ユーザーにメッセージを返します。
[
{
"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<function>\u001b[0m",
"type_": "String -> ( Bool, String )"
},
{
"input": "isGoodName \"Tom\"",
"value": "(\u001b[96mTrue\u001b[0m, \u001b[93m\"name accepted!\"\u001b[0m)",
"type_": "( Bool, String )"
}
]
タプルはとても便利な場面もありますが、それによってコードが複雑になり始めたときは、タプルではなくレコードを使うほうがいいでしょう。
レコード
レコードは多くの値を保持でき、さらに、それぞれの値は名前に関連付けられています。
以下はイギリスの経済学者ジョンA.ホブソンを表すレコードです。
[
{
"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 : number, first : String, last : String }"
},
{
"input": "john.last",
"value": "\u001b[93m\"Hobson\"\u001b[0m",
"type_": "String"
}
]
ジョンの名前と年齢に関する3つのフィールドを持つレコードを定義しました。
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 }"
}
]
上記の式を声に出して読み上げるとしたら「姓がAdamsになった新しいバージョンのjohnのが欲しい」とか「年齢が22歳のjohn」などと言いますよね。
john
の一部のフィールドを更新すると、まったく新しいレコードが作成されることに注意してください。既存のレコードにあるフィールドが上書きされることはありません。Elmはできるだけ多くの内容を共有することによって効率的にレコードの更新を行います。10個あるフィールドの1つを更新するとき、新しいレコードは変更されていない9つの値を既存のレコードと共有します。
これらを踏まえて年齢を更新する関数を書いてみると次のようになるでしょう。
[
{
"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 }"
}
]
このようにしてレコードのフィールドを更新することは本当によくあります。次の章ではさらに多くの例を見ていきます!