はじめに
皆さん、Elm書いてますか?私も最近入門してみたところです。
Elmの文法はHaskellをベースにしているだけあって似たようなところが多く、Haskellの経験がある人にとっては書きやすい言語であると思います。ただ、Haskellの問題点を解決するためであったり、Elm特有の事情のためにHaskellと若干文法が異なっているケースがあります。
そこでこの記事ではHaskellの経験がある人に向けて、ちょっと引っかかりやすいポイントを含めた説明していきたいと思います。
Elmのreplかオンラインエディタを用意して、打ち込みながら試していきましょう!
文法一覧
ではさっそくざっくりと文法の一覧を紹介していきましょう!
リテラル
リテラルにはざっくり以下の3つが存在します。
- 数値
- 真偽値
- 文字
それぞれ詳しく見ていきましょう。
数値リテラル
> 37
37 : number
> 2.718
2.718 : Float
最終的にJavaScriptに変換されるため、Haskellと勝手が異なります。単に整数を記述するとnumber
に推論され、その後利用箇所に応じてInt
またはFloat
として扱われます。小数を記述するとFractional
ではなくいきなりFloat
型に定まります。
真偽値リテラル
> True
True : Bool
> not True
False : Bool
これはHaskellと一緒ですね。大文字から始まるところも、否定に!
ではなくnot
を使うところも同じです。
文字に関するリテラル
> 'c'
'c' : Char
> "abc"
"abc" : String
シングルクォートがChar
型、ダブルクォートがString
型なのは一緒です。蛇足ですがHaskellと違ってString
型は[Char]
のエイリアスではないことに注意してください。
また、複数行文字列リテラルを扱うことができます。ダブルクォート3つで挟んでください。内部でダブルクォートを使用することもできてとっても便利です。ただしReplでは試しにくいです。
"""
Elm Kansai!
Let's "enjoy"!!!
"""
関数定義
square : Int -> Int
square n = n * n
これもHaskellではよく見かける定義方法とほぼ一緒ですね。型を書いてから、関数の中身を定義していきます。ただし先ほどからちょくちょく出てきていますが、型の指定にはひとつだけのコロン:
を使います。Haskellではふたつ::
だったのでクセが残っている人は超注意です!
また、Haskellと同様にトップレベルで定義する関数にはドキュメントを兼ねて型を書くのがベターです。また、不必要に広い範囲の型推論を避けて、想定外の利用を防止することができるメリットもあります。
Lambda式
f = \n -> n * n
mapの引数とか割といろんなところで使いたくなるLambda式。これもHaskellと一緒で$\lambda$を模したバックスラッシュ\
を使って定義します。
リスト
> [1, 2, 3, 4, 5]
[1,2,3,4,5] : List number
> 1 :: [2, 3, 4, 5]
[1,2,3,4,5] : List number
> 1 :: 2 :: 3 :: []
[1,2,3] : List number
カンマ区切りで書けるのは他の言語ともだいたい一緒です。ここでもやはりHaskellと一緒で、空リスト[]
に結合関数::
でくっつけていく方式でリストが成り立っています。ただしHaskellではリストの先頭に要素を結合する関数が:
だったのに対してElmでは::
になっていることに注意が必要です。関数と逆ですので!
タプル
t = (False, "Foo", Just 5)
(name, age) = ("Thomas", 37)
リストと違って固定長で、中身の要素は型が違っても大丈夫なのがタプルです。これもHaskellとの違いは無いので大丈夫でしょう。
レコード
type alias Point =
{ lat : Float
, lon : Float
}
p1 =
{ lat = 139.744858
, lon = 35.675888
}
Haskellではdata
でレコードを作りますが、Elmでは型エイリアスで定義します。あとは代入したり、関数の型に書いたりして使うだけです。
また、レコード内の値の読み取りは.field_name
でアクセスします。これは関数として定義されますが、JS風に後ろにくっつけることもできます。レコード内のフィールドと同名の値取り出し用関数が定義されるHaskellとは異なるところですので注意が必要ですね。例えば今回の場合は以下のようにアクセスします。関数としても使えるので、map
の引数にも簡単に与えられます!
> .lat
<function> : { b | lat : a } -> a
> p1.lat
139.744858 : Float
> .lat p1
139.744858 : Float
> List.map .lat [p1]
[139.744858] : List Float
ただし注意が必要な点として.lat
は「何でも良いけどlat
を含むレコードの中にあるlat
の値を取ってくる」関数であって「Point
だけを引数に取る」関数ではないというところです。Point
とは全然関係ないlat
というフィールドを持ったレコードに対しても使用できてしまいます。
> a2 = {lat = "FOOOOO!!"}
{ lat = "FOOOOO!!" } : { lat : String }
> .lat a2
"FOOOOO!!" : String
if式
n = 5
s = if (n < 5) then "It's small" else "It's big!"
おなじみif式です。else
が必須であること、返す値の型を全て同一にしなければならないことも含めてHaskellとの違いも特にありません。筆者は他の言語と行ったり来たりしているうちによくthen
を書き忘れてしまいがちなので、皆さんもお気を付けください。
case式
maybeInt = Just 5
m = case maybeInt of
Just n -> n
Nothing -> 0
これもHaskellと一緒です。中でパターンマッチを使うこともできるのでMaybeやList、レコードを使うときなどに重宝するでしょう!
let-in式
let
a = (2 - 1) ^ 2
b = (3 - 5) ^ 2
in
sqrt (a + b)
これもHaskellでおなじみin
の中で使う値をlet
で計算しておける便利構文です。なおElmではlet-in
式で十分という理由1からwhere
を実装していません。今後もおそらく実装されない見通しなので諦めましょう。
カッコの省略
> List.map ((+) 10) (List.filter ((>) 5) (List.range 1 10))
> List.map ((+) 10) <| List.filter ((>) 5) <| List.range 1 10
[11,12,13,14]
ちょっと文法とは異なりますがせっかくなので紹介。カッコばかりで見づらくなるためHaskellでよく使う($)
はElmでは<|
と書きます。右から順番に適用していくイメージですね。
パイプ
> 5 |> (\n -> n + 10) |> String.fromInt
"15" : String
先ほどのものと対になるこちらも紹介。Unixでおなじみのパイプを再現したような動きをするパイプ|>
がElmにはあります。実はHaskellにもData.Function
に(&)
という関数が定義されていて同じ事ができます2。
個人的に、Elmではどちらかというと左から自然に読めるパイプの方が好まれている印象を受けました。
コメント
-- 1 line comment
{-
Multiple
Lines
Comment
{- and nested! -}
-}
これは完全にHaskellと同一ですね。ネストできるところも使いどころによっては便利かもしれないです。
型クラス
> List.map (\n -> n + 1) (List.range 1 10)
[2,3,4,5,6,7,8,9,10,11]
> Maybe.map (\n -> n + 1) (Just 5)
Just 6
Elmにはいわゆる型クラスに相当するものが実装されていません。個別のクラスそれぞれでmap
やfilter
などが定義されています。従って先ほどからちらほら出てきているように、利用時にはプレフィックスが必要となります。
ただし例外的にappendable
, comparable
, number
の型はいわゆる型クラス的な動きをします3。しかし自作型をそれらのサブクラスにしたりすることはできません。また、自分でこのような働きをするクラスを定義することもできません。
> (++)
<function> : appendable -> appendable -> appendable
> [1, 2, 3] ++ [4, 5, 6]
[1,2,3,4,5,6]
: List number
> "Foo" ++ "Bar"
"FooBar" : String
> (>)
<function> : comparable -> comparable -> Bool
> "foo" > "bar"
True : Bool
> 1 > 2
False : Bool
> (+)
<function> : number -> number -> number
> 1 + 2
3 : number
> 1.5 + 5.3
6.8 : Float
おわりに
元々Haskell界隈からこの言語を知ったので、主にHaskellとの違いに触れつつ文法に関する事項について整理してみました。ほぼ一緒なところもありつつ、微妙に違ったりするところもあるのでHaskellから移ってきた人は若干注意が必要かもしれません。
JSや設計思想との兼ね合いでHaskellからは思い切って機能を削ってきているところもありますが、Webフロント開発をする上で十分であろうとは思います。そういうのをバリバリ使いたい人はGHCJSなどを検討すると良さそうです。
とにもかくにもReplで気軽に試しつつ、Elmの世界に浸ってもらえればと思います!
-
このほかに
compappend
というListとStringを統一的に扱う型が定義されていますが、コンパイラが使うだけでユーザー側から扱うことはありません。 ↩