言語の基礎

この節では、Elmの言語の基礎についてざっと見ていきましょう。

以下の説明の流れをたどっていくときには、REPLを使って実際に手を動かしながら読み進めていくと理解しやすいので、インストールが終わったらターミナルでelm replを実行してみてください。ターミナルに次のようなものが表示されるはずです。

---- Elm 0.19.0 ----------------------------------------------------------------
Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
--------------------------------------------------------------------------------
>

このREPLは入力のたびにその結果の型を出力してくれますが、Elmの概念を徐々に導入していけるように、このチュートリアルでは型注釈は省略することにします。

ここでは関数リストタプルレコードを取り扱います。これらの言語の要素はいずれも、JavaScriptやPython、Javaのような言語にある構造とよく似たものです。

まずは文字列から始めましょう。

> "hello"
"hello"

> "hello" ++ "world"
"helloworld"

> "hello" ++ " world"
"hello world"

Elmでは文字列の連結に(++)演算子を使います。これらの文字列は厳密にそのまま連結されることに注意してください。"hello""world"を連結したとき、その結果には空白文字は含まれません。

数式の見た目はごく普通です。

> 2 + 3 * 4
14

> (2 + 3) * 4
20

JavaScriptとは異なり、Elmは整数と浮動小数点数を区別します。ちょうどPython 3と同じように、浮動小数点数の除算(/)と整数の除算(//)の両方が別々に存在します。

> 9 / 2
4.5

> 9 // 2
4

関数

引数にとった数がゼロよりも小さいかどうかを調べる、isNegative関数を書いてみましょう。この関数の結果は、TrueFalseになるでしょう。

> isNegative n = n < 0
<function>

> isNegative 4
False

> isNegative -7
True

> isNegative (-3 * -4)
False

JavaScriptやPython、Javaのような言語とは関数適用の見た目が異なっていることに注目してください。括弧の中にすべての引数をカンマで区切って書くのではなく、関数を適用するのには単に空白を使います。そのため、(add(3,4))(add 3 4)になり、結果として括弧やカンマをたくさん書いてコードが長くなるのを避けることができるのです。いったんこれに慣れてしまえば、括弧やカンマを使う構文よりもずっと読みやすく感じられるようになるはずです! elm/htmlパッケージを見ると、この関数適用の構文のお陰でコードが読みやすく保たれているのがわかると思います。

無名関数を使えば、この関数を次のように定義することもできます。

> \n -> n < 0
<function>

> (\n -> n < 0) 4
False

名前が付いていないだけで、この無名関数はisNegativeと同じものです。また、(\n -> n < 0) 4に付けられた括弧は重要です。矢印に続けて、Elmはなるべく長くコードを読み続けようとします。この括弧はその範囲を制限し、関数本体がどこで終わるのかを示しているのです。これによってElmは4が関数の引数であるとわかるのです。

Note: 目を細めて見ると、無名関数の先頭のバックスラッシュはラムダ(λ)と似ているように見えると思います。これはひょっとしたら、Elmのような言語を産んだ数学の歴史的背景を、何気なく覗き見てしまっているのかもしれませんね。

If式

Elmで条件に応じて振る舞いを変えたいなら、if式を使うといいでしょう。

> if True then "hello" else "world"
"hello"

> if False then "hello" else "world"
"world"

このifthenelseという予約語は、条件部分とふたつの分岐部分を区切るのに使われており、丸括弧や波括弧を使う必要はまったくありません。

Elmは"truthiness"の概念を持たず、数や文字列、リストを真偽値として使うことはできません。もしそうしようとすると、本当の真偽値を使う必要があることをElmは教えてくれます。

次は9000以上の数であるかどうかを教えてくれる関数を作ってみましょう。

> over9000 powerLevel = \
|   if powerLevel > 9000 then "It's over 9000!!!" else "meh"
<function>

> over9000 42
"meh"

> over9000 100000
"It's over 9000!!!"

REPLでバックスラッシュを使うと、1行のコードを複数行に分割して入力することができます。上のover9000の定義でこれを使っていますね。それから、関数の本体でいつも1行下げるのはElmの良い習慣です。こうすると一貫性があり読むのが楽になるので、通常はコード中のすべての関数や値についてこのように字下げしたくなると思います。

Note: 関数の二行目の先頭には、空白文字を追加するようにしてください。「構文として意味のある空白」、つまりElmはその構文の一部としてインデントを持っているのです。

リスト

リストはElmでも最もよく使われるデータ構造のひとつです。リストは互いに関連する値の連続を保持するもので、JavaScriptの配列にも似ています。

リストは複数の値を持つことができますが、それらの値はすべて同じ型を持っていなければなりません。例として、Listモジュールから幾つかの関数を見てみましょう。

> names = [ "Alice", "Bob", "Chuck" ]
["Alice","Bob","Chuck"]

> List.isEmpty names
False

> List.length names
3

> List.reverse names
["Chuck","Bob","Alice"]

> numbers = [1,4,3,2]
[1,4,3,2]

> List.sort numbers
[1,2,3,4]

> double n = n * 2
<function>

> List.map double numbers
[2,8,6,4]

繰り返しますが、リストのすべての要素は同じ型を持っていなければならないことに注意してください。

タプル

タプルはリストとはまた異なった便利なデータ構造です。タプルは固定された個数の値を保持することができ、それらの値の型はそれぞれ別々にすることができます。典型的な使いかたとしては、関数からふたつ以上の値を返す必要があるときです。次の関数は名前を受け取り、ユーザにメッセージを返します。

> import String

> goodName name = \
|   if String.length name <= 20 then \
|     (True, "name accepted!") \
|   else \
|     (False, "name was too long; please limit it to 20 characters")

> goodName "Tom"
(True, "name accepted!")

タプルはとても便利な場面もありますが、それによってコードが複雑になり始めたときは、タプルではなくレコードを使うほうがいいでしょう。

レコード

レコードはJavaScriptやPythonのオブジェクトに似たデータ型で、レコードは任意個のフィールドを持っていて、それぞれのフィールドに値を格納したり、フィールドから値を取り出すことができます。ただしElmのレコードのフィールドは固定されていて、レコードに動的にフィールドを付け加えたり取り除いたりすることはできません。レコードはElmではとても頻繁に使われる便利なものであることがすぐにわかるでしょう! いくつか簡単な例を見ていきます。

> point = { x = 3, y = 4 }
{ x = 3, y = 4 }

> point.x
3

> bill = { name = "Gates", age = 62 }
{ age = 62, name = "Gates" }

> bill.name
"Gates"

波括弧を使うとレコードを作ることができ、フィールドにアクセスするにはドットを使います。Elmのレコードアクセスには、関数のように振る舞う別の構文もあります。変数名の先頭にドットを付けると、「次の名前でフィールドにアクセスしてください」と言っていることになります。.nameはレコードのnameフィールドを取り出す関数であるという意味です。

> .name bill
"Gates"

> List.map .name [bill,bill,bill]
["Gates","Gates","Gates"]

関数がレコードを引数にとるときは、パターンマッチを使えば少しコードをわかりやすくすることができます。

> under70 {age} = age < 70
<function>

> under70 bill
True

> under70 { species = "Triceratops", age = 68000000 }
False

数値型のageフィールドを持っているレコードであれば、どんなレコードであっても渡すことができます。

レコードが持つ値を更新するときに便利な、次のような構文もあります。

> { bill | name = "Nye" }
{ age = 62, name = "Nye" }

> { bill | age = 22 }
{ age = 22, name = "Gates" }

この構文ではレコードの破壊的な更新をしているのではないことに注意してください。billのフィールドを更新したとき、実際には既存のレコードを上書きしているのではなく、新たなレコードが作成されています。効率のため、Elmは新しいレコードの内容を古いレコードとなるべく共有しようとします。もし10個のフィールドのうちのひとつを更新したとしたら、変更されていない残りの9個の値は新しいレコードも共有します。

レコードとオブジェクトの比較

ElmのレコードはJavaScriptのオブジェクトと似ていますが、幾つか重要な違いもあります。レコードには次のような特徴があります。

  • 存在しないフィールドにアクセスすることはできません。
  • フィールドが undefinednull になることもありません。
  • thisself キーワードを使って再帰的なレコードを作ることはできません。

Elmではデータとロジックを厳格に分離することが推奨されますが、この分離を破壊するのは主に this だと言って差し支えないでしょう。これはオブジェクト指向言語のシステム上の問題であり、Elmはこの問題を意図的に避けています。

レコードは構造的部分型も提供しており、Elmでは必要なフィールドが存在している限りは、そのレコードを関数の引数などとして使うことができます。これにより、信頼性について妥協することなく、柔軟性も得ることができるのです。

results matching ""

    No results matching ""