LoginSignup
9
1

More than 5 years have passed since last update.

初心者Elm入門(その4:削除機能付きToDoリスト)

Last updated at Posted at 2019-04-25

はじめに

初心者Elm入門その3で紹介したElmアーキテクチャを使ってもう少し複雑な例に取り組んでみよう。

削除機能付きToDoリスト

完成画面はこのようになる。
スクリーンショット 2019-04-25 11.22.15.png
入力フィールドに文字を入力すると「Add ToDo」ボタンが押せるようになり、ToDoリスト中の文字をクリックすることで削除できる。
さっそくコードを見よう。
まずは自力バージョン。
のちに改良バージョンも示す。

ToDoList.elm
module Main exposing (main)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)


main : Program () Model Msg
main =
    Browser.sandbox
        { init = init
        , update = update
        , view = view
        }



-- MODEL


type alias Model =           -- 今回のModelはレコード。
    { input : String         -- 入力フィールド中の文字列を保持する。
    , memos : List String    -- 文字列を要素とするリスト。
    }                        -- 上記の例では memos = ["モンドール", "ワイン", "醤油", "卵", "食パン"]
                             -- のようになる。submitされた文字列を格納する。

init : Model                 -- input初期値は""、memos初期値は[]
init =
    { input = ""
    , memos = []
    }



-- UPDATE


type Msg
    = Input String
    | Submit
    | Done Int


update : Msg -> Model -> Model
update msg model =
    case msg of
        Input input ->                   -- 入力フィールドの文字が変わるたびに
            { model | input = input }    -- inputとして受け取る。

        Submit ->                        -- 「Add ToDo」ボタンが押されると
            { model
                | input = ""             -- 入力フィールドを空にし
                , memos = model.input :: model.memos    -- memosの先頭にinput文字列を加える。
            }

        Done done ->              -- done(Int型)番目の要素がクリックされると
            { model
                | memos = List.take done model.memos ++ List.drop (done + 1) model.memos
            }            -- 0~(done-1)番目までのmemosと
                         -- (done+1)~最後までのmemosが連結されたリストを
                         -- 新しいmemosとする。

-- VIEW


view : Model -> Html Msg
view model =
    div []
        [ Html.form [ onSubmit Submit ]
            [ input [ value model.input, onInput Input ] []
            , button
                [ disabled (String.length model.input < 1) ]    -- inputのlengthが1未満だとボタンが押せない。
                [ text "Add ToDo" ]
            ]
        , ul [] (viewMemo model.memos)    -- viewMemo関数の引数としてmemos(List String)をとる。
        ]


htmlFunc : ( Int, String ) -> Html Msg    -- ややこしいので地の文で説明します。
htmlFunc tuple =
    let
        i =
            Tuple.first tuple

        memo =
            Tuple.second tuple
    in
    li [ onClick (Done i) ] [ text memo ]


viewMemo : List String -> List (Html Msg)
viewMemo memos =
    let
        memoIndexPair =
            List.indexedMap Tuple.pair
    in
    List.map htmlFunc (memoIndexPair memos)    -- (*)

(*) memoIndexPair関数はList Stringを渡すと[(0, "文字列0"), (1, "文字列1"), ...]というタプルを要素とするリストを返してくれる。
例ではmemos = ["モンドール", "ワイン", "醤油", "卵", "食パン"]なので、memoIndexPair memos[(0, "モンドール"), (1, "ワイン"), (2, "醤油"), (3, "卵"), (4, "食パン")]となる。
これに対してList.mapで各々の要素についてhtmlFuncを適用する。
htmlFuncはタプルの0番目の要素をiとし、1番目の要素をmemoとしてHtml Msgであるli [ onClick (Done i) ] [ text memo ]を作る。
これで完成!......なんだけど、会社の先輩が「もっと簡単に書けるよ!」って教えてくれたのでちょっと改良する。

削除機能付きToDoリスト(改)

以下改良バージョン。
変更したのはややこしかったhtmlFuncとviewMemoの部分のみ。

ToDoList2.elm
module Main exposing (main)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)


main : Program () Model Msg
main =
    Browser.sandbox
        { init = init
        , update = update
        , view = view
        }



-- MODEL


type alias Model =
    { input : String
    , memos : List String
    }


init : Model
init =
    { input = ""
    , memos = []
    }



-- UPDATE


type Msg
    = Input String
    | Submit
    | Done Int


update : Msg -> Model -> Model
update msg model =
    case msg of
        Input input ->
            { model | input = input }

        Submit ->
            { model
                | input = ""
                , memos = model.input :: model.memos
            }

        Done done ->
            { model
                | memos = List.take done model.memos ++ List.drop (done + 1) model.memos
            }



-- VIEW


view : Model -> Html Msg
view model =
    div []
        [ Html.form [ onSubmit Submit ]
            [ input [ value model.input, onInput Input ] []
            , button
                [ disabled (String.length model.input < 1) ]
                [ text "Add ToDo" ]
            ]
        , ul [] (viewMemo model.memos)
        ]


htmlFunc : Int -> String -> Html Msg
htmlFunc i memo =
    li [ onClick (Done i) ] [ text memo ]


viewMemo : List String -> List (Html Msg)
viewMemo =
    List.indexedMap htmlFunc

とてもすっきりした。
しかし初心者としてはなぜ動くのかが分からない(分からなかった)。
まずviewMemoに引数がないのが意味が分からない。
これは関数と関数を合わせて関数を作るというものらしい(部分適用)。
具体例を示す。

> (*)
<function> : number -> number -> number
> (*) 2 3
6 : number
> f = (*) 2
<function> : number -> number
> f 3
6 : number

(*)というのは第1引数と第2引数のかけ算をする関数だ。

f = (*) 2とすると(*)の第1引数が2に固定された関数fを作ることができる。
よってf 36となる。
ここで

> f = (*) 2
<function> : number -> number
> List.map
<function> : (a -> b) -> List a -> List b
> List.map f [1, 2, 3, 4, 5]
[2,4,6,8,10] : List number
> g = List.map f
<function> : List number -> List number
> g [1, 2, 3, 4, 5]
[2,4,6,8,10] : List number

List.map 関数 リストはリストの要素ひとつひとつに関数を適用して展開するものである。
上記例では[1, 2, 3, 4, 5]の各要素を2倍して展開する。
g = List.map fと置くことでfしながらmapもしてくれる関数をgと呼ぶことにしたということ。

ToDoList.elmに戻ろう。
List.indexedMapの定義は(Basics.Int -> a -> b) -> List.List a -> List.List bなので、この第1引数をInt -> String -> Html MsgであるhtmlFuncに固定してやる。
そうすることで、List Stringが来たときにList (Html Msg)を返す関数viewMemoが出来る。
viewMemoはList.indexedMapとhtmlFuncの機能を同時に行ってくれる関数と見ることが出来るのでmemosの要素番号と要素が取得出来てli [ onClick (Done i) ] [ text memo ]が得られる。

試し方

  • 適当なディレクトリを作る。
  • elm initする。
  • srcというディレクトリができているのでsrc内にToDoList.elmを置く。
  • elm make src/ToDoList.elm --output=todolist.htmlする。(--outputは必須ではないが指定しないとindex.htmlという名前でコンパイルされる。)
  • todolist.htmlが出来るのでブラウザで開く。

初心者Elmシリーズ

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1