こんにちは、iOS開発チームの本柳です。
以前、弊社のブログにはじめようElm!という投稿でプログラミング言語のElmについて簡単に紹介しました。
Elmについて、興味がある方は是非一度御覧ください。
さて、今回はElmを書く上で基本的なシンタックス*1について書いていこうと思います。
コメント
-- 1行コメント {- 復 数 行 コ メ ン ト -}
復数行コメントについては下記のように書くことで、簡単にコメントのイン/アウトを変更できるそうです。
{-- コメント --} {--} "複数行コメントの開始行末に`}`をつけることで簡単にコメントアウト出来る!" --}
文字リテラル
シングルクオートで文字をくくるとchar型になります。 char型は1文字しか指定出来ません。
ダブルクオートで文字をくくるとString型になります。
また、"""
〜"""
で文字をくくることで複数行の文字を定義することが出来ます。
-- char type 'a' -- String type "abc" -- multi line string """ a b c """
演算子
論理演算子
ElmではAND、OR、否定演算子にはBoolenaのみ指定出来ます。
-- AND演算 True && False --> True -- OR演算 True || False --> False -- 否定 not True --> False
文字結合
"a" ++ "b" --> "ab"
算術演算子
-- 加算 1 + 2 --> 3 -- 減算 1 - 2 --> -1 -- 乗算 2 * 8 --> 16 -- 冪乗 2 ^ 8 --> 256 -- 除算 7 / 2 --> 3.5 -- 除算(切り捨て) 7 // 2 --> 3 -- 剰余 7 % 2 --> 1
同値比較
同値比較は両辺の値が同じ型である必要があります。
"a" == "a" --> True "a" == "b" --> False "a" /= "a" --> False "a" /= "b" --> True
対照比較
対照比較はcomparable
型である必要があります。
comparable
型はドキュメントによると、number
, character
, string
, list
, tuple
が該当するようです。
-- 左辺が右辺より大きいか? 1 > 2 --> False -- 左辺が右辺より小さいか? 1 < 2 --> True -- 左辺が右辺以上か? 1 >= 2 --> False -- 左辺が右辺以下か? 1 <= 2 --> True
if文
Elmのif
は式なので、評価結果を返します。
-- 1行で書く if True then "a" else "b" -- 複数行で書く if False then "a" else "b" -- else if count = 1 if count < 0 then "count less than zero." else if count == 0 then "count be zero." else "count over the zero."
case文
Elmのcase
は下記のように記述します。
case [式] of [条件1] -> [結果] [条件2] -> [結果] _ -> [結果] -- `_`は先に記述した条件以外の場合にマッチします
Elmのcase
は式なので、評価結果を返します。
level = 99 case level of 1 -> "Level min." 99 -> "Level max!" _ -> "Other level." --> Level max!
List
Listに格納する値の型は全て同じである必要があります。
[1,2,3,4] --> List number ['a', 'b', 'c'] --> List Char ["a", 'a'] --> Compile Error! -- Listの先頭に値を追加するOperator 1 :: [] --> [1] 1 :: [2] --> [1,2] 1 :: 2 :: [3,4] --> [1,2,3,4]
タプル
復数の値の集合を定義出来るのがタプルです。
タプルにはListと異なり、異なる型の値を定義することが出来ます。
tpl = (1, "a")
タプルで定義した値はfst
、snd
という関数で取り出すことが出来ます。
tpl = (1, "a") fst tpl --> 1 snd tpl --> "a"
3つ以上のタプルの場合、3番目以降の値を取り出す関数は存在しないので、下記のように取り出す必要があります。
thrd tpl = let (_,_,theThird) = tpl in theThird thrd (1,2,3) --> 3
※ let式
については後述します。
関数
関数名 引数1 引数2 ... = 式
というシンタックスで定義します。
hello name = "Hello " ++ name ++ "!!" hello "Tom" --> "Hello Tom!!" -- 復数の値を返したいときはタプルを利用します multiReturn n = (n*2, n^2) multiReturn 3 --> (6, 9)
無名関数も定義することが出来ます、\引数 ... -> 式
というシンタックスで定義します。
power = \n -> n^2 power 2 --> 4 add = \a b -> a + b add 1 2 --> 3
UnionType
型の集約を定義することが出来ます。
UnionTypeはcase
と組み合わせて利用すると時、コンパイラが全てのマッチ条件を網羅しているかをチェックしてくれるので非常に便利です。
-- OkとNgという型を集約するConditionを定義 type Condition = Ok | Ng -- 定義したConditionからBoolを返す関数を定義 checkCondition condition = case condition of Ok -> True Ng -> False checkCondition Ok --> True checkCondition Ng --> False
Record
Elmの構造体のようなものです。
{ パラメタ名 = 値, ... }
といった形式で定義し、パラメタへのアクセスには.
を使って次のようにアクセス出来ます。record.parameter
point = { x = 3, y = 4 } point.x --> 3 point.y --> 4
Recordはパラメタの一部を更新して、新たなRecordを作成することが出来ます。
point1 = { x = 1, y = 2 } point2 = { point1 | x = 3 } -- point1のxを3にした、新しいpoint2を生成している point1.x --> 1 point1.y --> 2 point2.x --> 3 point2.y --> 2
let式
let 変数の定義 in 定義した変数を利用する式
といった構文で、スコープが限定的な変数の定義と利用を記述することが出来ます。
let a = 1 b = 2 in a + b --> 3 a + b --> Compile Error!
型注釈・型エイリアス
Elmは基本的にコンパイラが型を推論してくれますが、自前で型の定義を記述しておくことも出来ます。
-- 関数の定義は `引数 -> 引数 -> 戻り値` のように戻り値が最後の型になるように `->` で繋げて型を定義していきます。 add : Int -> Int -> Int add a b = a + b -- 型にはRecordを書くことも出来ます scale : Int -> { width: Int, height: Int } -> { width: Int, height: Int } scale rate size = { size | width = size.width * rate, height = size.height * rate }
型にはエイリアスを定義することが出来ます。
Recordに定義しておくと便利です。
type alias Size = { width: Int, height: Int } scale : Int -> Size -> Size scale rate size = { size | width = size.width * rate, height = size.height * rate }
パイプラインオペレーター
ElmにはF#やElixirで利用されるパイプラインオペレーターが存在しています。
以下の関数を元に使い方を見てみましょう。
-- 整数を引数にとって、引数を二乗じて返す関数 power : Int -> Int power n = n^2
Elmでは、関数を引数にとる関数を書く時、素直に書くと以下のようになります。 引数にとる値が、復数の関数の組み合わせになっていくと可読性が著しく下がってしまいます。
power (power (power 2)) --> 256
このような記述を簡潔に書くためには|>
、<|
オペレーターを利用します。
|> オペレーター
|>
オペレーターはa -> (a -> b) -> b
のように定義されています。
2 |> power --> 4 -- 上記のコードは、このコードと同義です(x |> f == f x) power 2 --> 4 -- 繋げて書くことも出来ます 2 |> power |> power |> power --> 256
<| オペレーター
<|
オペレーターは(a -> b) -> a -> b
のように定義されています。
power <| 2 --> 4 -- 上記のコードは、このコードと同義です(f <| x == f x) power 2 --> 4 -- 繋げて書くことも出来ます power <| power <| power 2 --> 256
Module
Elmでは外部に定義したModuleの読み込む場合、それを宣言的に記述する必要があります。
-- 基本的な読み込み方 import Module Module.func a -- `as`キーワードを使うことでModuleに別名をつけることが出来ます import Module as M M.func a -- `exposing`で利用する関数を読み込める import Module exposing(func) func a -- 復数の関数を読み込みたいときは`exposing(a, b, c, ...)`のように記述する import Module exposing(func1, func2) func1 a func2 b -- `exposing(..)`と記述することで、Moduleに定義された関数を全て読み込むことが出来る import Module exposing(..) func1 a func2 b
Elmではデフォルトの状態で下記のModuleが読み込まれているので、これらの機能を利用したい場合は自分でimportせずに利用することが可能です。
import Basics exposing (..) import Debug import List exposing ( List, (::) ) import Maybe exposing ( Maybe( Just, Nothing ) ) import Result exposing ( Result( Ok, Err ) ) import Platform exposing ( Program ) import Platform.Cmd exposing ( Cmd, (!) ) import Platform.Sub exposing ( Sub )
Elmのシンタックスについて、ざっくりと書いてみました。
とりあえず、このあたりを抑えておけばElmのコードはすんなりと読めるようになるはずです。
よく分からない記述が出てきたときはドキュメントを見ればだいたいのことは分かりますね。
ちなみに、このブログは業務時間の一部を利用して書いています*2。
「技術的な知見をどんどん公開していきたいんだけど、時間がない…」なんて悩みをお持ちのエンジニアでもガラパゴスなら大丈夫!
ご応募、お待ちしております。