Skip to content

Instantly share code, notes, and snippets.

@asmasa

asmasa/elm.md Secret

Last active January 27, 2018 00:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asmasa/a6d3b17d327ad74216b644e1772b593b to your computer and use it in GitHub Desktop.
Save asmasa/a6d3b17d327ad74216b644e1772b593b to your computer and use it in GitHub Desktop.
Elm文法

Elm言語資料

前提としているElmのバージョンは0.18.0

  • 実行環境
  • リテラル
  • 関数
  • 構文
  • モジュール
  • Elmアーキテクチャ
  • Htmlモジュール
  • Tips

実行環境

多分何種類かあると思うけど、代表的なモノを挙げる。

REPL

  • elmにはREPL(Read-eval-print loop)という対話型評価環境がある。
  • ようはターミナルでElmを試せる
  • 複数行に跨るソースを打ち込む場合は、\を行末につける必要がある。
$ elm-repl

elm-reactor

  • 単純なアプリケーションを作る時につかう
  • 外部JS、外部CSSの読み込みには対応していない
$ elm-reactor

ellie

Webpack

  • ちゃんとしたアプリケーションを作る時に使う

リテラル

-- Char
'a'

-- String
"Hello World!!"

-- Bool
True
False

-- Int(ただしREPLで打ち込むと、Int or Floatを表すnumberと解釈される
1000

-- Float
0.1

-- Tuple(値の組み合わせ。異なる型の組み合わせも可能。)
(1, "test")

関数

  • 関数は、同じ引数に対して常に同じ結果を返す
  • 引数の状態を変えてはいけない。Elmはimmutableな世界なので、変えれないようになっている。

名前付き関数

  • 名前で呼び出して使う
  • 1行目のadd : Int -> Int -> Intは型注釈(Type Annotation)と言う
  • 以下はIntの引数を2つ受け取って、Intの戻り値を返す関数
add : Int -> Int -> Int
add x y =
    x + y


-- 呼び出し方
add 3 4

無名関数

  • 関数に渡して使う
  • 最初に\を付ける
  • 以下は引数を2つ受け取って、足し合わせた結果を戻り値として返す関数
\x y -> x + y

引数なし関数

  • 定数となる
name : String
name =
    "hoge"

関数を引数/戻り値に使える

-- 関数が引数
funcArg: (Int -> Int) -> Int -> Int
funcArg func x =
    func x

-- 使い方
funcArg (\x -> 2 * x) 2


-- 関数が戻り値
funcReturn : (Int -> Int)
funcReturn =
    \x -> 2 * x

-- 使い方
func =
    funcReturn

func 2

グループ化

  • ある関数の結果を直接使って別の関数を呼び出したい場合に使う
  • ()を使う
multiply : Int -> Int -> Int
multiply x y =
    x * y

add 3 (multiply 1 2)
  • グループ化しないと、コンパイラにaddの引数としてみなされてしまう。
  • なおパイプ演算子を使うと()を外して見やすくすることができる。
  • ListやMaybeの関数呼び出しなどでよく使う
2
    |> multiply 1
    |> add 3

型変数

  • 他の言語で言う型パラメータとかのこと
  • 関数をジェネリックにする
  • 後述のListMaybeなど型でも使われる
-- aとbが型変数な関数
switch : ( a, b ) -> ( b, a )
switch ( x, y ) =
    ( y, x )

関数について更に詳しく

type alias

  • 既存の型に別名を付ける
  • 型が明確になり、ソース全体の見通しが良くなる。

コレよりも

func : String -> String -> Int

こっちの方がわかりやすい

type alias Id =
    String

type alias Name =
    String

func : Id -> Name -> Int

Union Type

  • 複数の定数を1つの型で扱える
  • 他の言語で言うenumのような感じ
type Color = Red | Blue | Green

-- 使い方
color =
    Red

値を持つ

  • 関連した情報を持つこともできる。ペイロードと呼ぶ。
type Color = Red | Blue | Green | RGB Int Int Int | NoneColor String

-- 使い方。
white =
    RGB 255 255 255

ネスト

type Text = Text String Color

-- 使い方
text =
    Text "hoge" Red

標準のモジュール内での例

type Bool = True | False

type Maybe a = Just a | Nothing

union typeについて更に詳しく。

Records

  • キーバリューのセットを持つ
  • type aliasを使うと、別名をコンストラクタ関数として使用できるようになる。
  • immutableである。つまり属性の値を変更することはできず、変更するには新しいレコードを作ることになる。
-- 宣言
type alias User =
    { id : Int
    , name = String
    }

-- 使い方
user =
    User 1 "hoge"

-- idを使いたい場合
user.id

-- 更新
newUser =
    { user | name = "new name" }

List

  • 他の言語で言うCollection系で最も使うモジュール
    • Coreでは他に、Dict, Set, Arrayがある
[1, 2, 3]
1 :: [2, 3]
1 :: 2 :: 3 :: []

Listの関数

  • ここにドキュメントがあるので詳細はそちらを見れば良いので、ここでは幾つかの関数を紹介する

filter

-- 型注釈
filter : (a -> Bool) -> List a -> List a

-- 使い方
List.filter (\x -> x % 2 == 0) [1, 2, 3]
  • 要素の判定をする関数とListを渡すと、判定結果を満たす要素のみの新しいListを返す
  • (a -> Boolean)を無名関数とすることが多い

map

-- 型注釈
map : (a -> b) -> List a -> List b

-- 使い方
List.map (\x -> toString x) [1, 2, 3]
  • 要素を変換する関数とListを渡すと、Listの要素一つずつに関数を適用して要素を変換する
  • abは違うので、型を変えることもできる

Maybe

  • 他の言語で言う、Option Optionalとか
  • ありをJust、なしをNothingで表す
  • null,nilとかundefinedがないのはMaybeのおかげ
  • ユニオン型で定義されている
type Maybe a
    = Just a
    | Nothing

Just 1

Maybeの関数

  • ここにドキュメントがあるので詳細はそちらを見れば良いので、ここでは幾つかの関数を紹介する

map

-- 型注釈
map : (a -> b) -> Maybe a -> Maybe b

-- 使い方
Maybe.map (\x -> toString x) (Just 1)
Maybe.map (\x -> toString x) Nothing
  • 要素を変換する関数とMaybeを渡すと
    • Justの場合は、要素に関数を適用して要素を変換
    • Nothingの場合は、何もしない

withDefault

-- 型注釈
withDefault : a -> Maybe a -> a

-- 使い方
Maybe.withDefault 0 <| Just 1
Maybe.withDefault 0 Nothing 
  • デフォルト値とMaybeを渡すと
    • Justの場合は、要素を取り出す
    • Nothingの場合は、デフォルト値を返す

構文

if

  • returnはない
  • 式なので何かしらの値を返す
value = 1

-- ifはStringを返す
if value == < 0 then
    "Negative number."
else if value == 0 then
    "Zero."
else
    "Positive number"

case

  • 他の言語で言うパターンマッチ
  • 式なので値を返す
  • Union Typeと組み合わせると強力。Union Typeで定義した値が全て使われていない場合はコンパイラが怒ってくれる。
-- Union Type
---- ペイロードの値を取り出せる
type Color = Red | Blue | Green | RGB Int Int Int
color = Red

case color of
    Red -> 
        "red"
    RGB r g b ->
        (toString r) ++ ":" ++ (toString g) ++ ":" ++ (toString b)
    _ -> 
        "other"

-- List
---- 再帰で使える
case [1, 2] of
    [] -> 
        ""
    head::tails -> 
        toString head

-- Maybe
case Just 1 of
    Just x -> 
        x
    Nothing ->
        0

let

  • 値を変数に入れて使えるようにする
  • 見通しが良くなるので使うことが多い
let
    a =
        1 + 1

    (b, c) =
        ( 2, 3 )
in
a * b * c
  • 変数への再代入はできない。つまり以下はコンパイルエラーになる。
let
    a =
        1 + 1

    a = 4
in
a

ループについて

  • 他の言語で言うforループ的なものはない
  • 同等なことをやりたい場合は、Collection系に定義されている関数を使うか再帰を使う。

モジュール

  • モジュール = ファイルという考え方
  • モジュール名はファイル名と一致していないといけない
    • Utils.StringUtilsモジュールなら、Utils/StringUtils.elmというファイルを作る

宣言

func : String
func =
    "hoge"

type Color = Red | Blue | Green

User =
    { name : String}

上記に対してモジュール宣言は以下となる。

-- 全てを公開する場合の宣言
module ModuleName exposing (..)

-- 特定の関数、型を公開する場合の宣言
module ModuleName exposing (func, Color(..), User)
  • ColorのみだとRedなどは公開されないので(..)が必要となる。

import

import ModuleName

ModuleName.func

-- 別名を付ける
import ModuleName as Mod

Mod.func

-- 直接インポート
import ModuleName exposing(func)
func
  • 関数は別名.関数、型はexposingで直接インポートが見やすい
  • 名前の競合はコンパイラに怒られる。

デフォルトインポート

ここにデフォルトでインポートされているモジュールが載っている。 記述されているモジュールはインポートしなくても使える。

Elm Architecture

  • Elmでフロントエンドアプリケーションを作るためのパターン
  • 以下の4つが大きな要素
    • Model
    • Update
    • View
    • Msg(Message)

Model

  • アプリケーションの全ての状態(データ)を持つ

Msg(Message)

  • アプリケーションで起きたコトを表す通知。union typeで定義する
  • 起きた時に取得した値を持つ。(プルダウンメニューの変更値等の入力情報)

Update

  • Msgを受け取り、解釈してModelを更新して返す。
  • 後続処理として何かやらしたい場合は、Msgを送信する。なければCmd.noneを返す。

View

  • Modelを使ってHTMLを生成する

関係図

elm_architecture

アプリのソースコード例

-- モジュール宣言
module Main exposing (..)

-- Htmlモジュールをインポート
import Html exposing (program, Html, text)


-- Elmのエントリポイント
main : Program Never Model Msg
main =
    program
        { init = init
        , view = view
        , update = update
        , subscriptions = always Sub.none
        }


-- Model
type alias Model =
    {}


-- Msg
type Msg
    = Hoge


-- アプリケーションの初期化関数
init : ( Model, Cmd Msg )
init =
    ( {}, Cmd.none )


-- Update
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Hoge ->
            ( model, Cmd.none )


-- View
view : Model -> Html msg
view model =
    text "Hello World!!"

Cmd

  • 次に何かをやらせたいことを書く
  • なにもない場合はCmd.noneを使う
  • ここにドキュメントがある

subscriptions

  • 外部入力(マウスイベント、キーボードイベント等)をアプリケーションに入れるためのサブスクリプション
  • 今回は使用しないのでSub.noneを設定している。

Htmlモジュール

  • viewを作るのに最初必要なのは以下の3つのモジュール。
  • これらを組み合わせてviewを生成する。

<button type="button" class="btn btn-primary">投稿</button>
  • これをElmに変換してviewとすると以下となる。
import Html exposing (button, text)
import Html.Attributes exposing (class, type_)

view model =
    button [ class "btn btn-primary", type_ "button" ] [ text "投稿" ]

要素、属性ともにそれぞれ関数の構造(引数及び戻り値)は同じなので、button要素とclass属性について見ていく。

button要素

-- 型注釈
button : List (Attribute msg) -> List (Html msg) -> Html msg
  • 第1引数は、button要素の属性のListとなる
  • 第2引数は、ネストする要素のListとなる
  • 戻り値はHtml型

class属性

-- 型注釈
class : String -> Attribute msg
  • 第1引数は、class属性の値
  • 戻り値はAttribute

HTML to Elm

Tips

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment