26
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Elm] Maybeを活用する

Last updated at Posted at 2019-09-25

Maybeの基本的な使い方

「Maybe」という名前からもわかるように、Maybeを使うと「ないかもしれない」状態を表現することができる。
Maybeの定義は、Just(ある場合)とNothing(ない場合)の2状態からなるCustomTypeである。

type Maybe a
    = Just a
    | Nothing

CustomTypeでcase文を用いて引数を取り出せるのと同様に、Maybeもcase文で値を取り出すことができ、ある場合とない場合で処理を分けることができる。

例)名前がある時はその名前を、名前がない時は"NO NAME"と出力する

getUserName : Maybe String -> String
getUserName userName =
    case userName of
        Just name ->
            name

        Nothing ->
            "NO NAME"

result =
    Debug.log "result"
        ( getUserName (Just "yuna")
        , getUserName Nothing
        )
    -- result: ("yuna","NO NAME")

Maybe.withdefault

Nothingだった時の初期値を与えることができる。
先ほどの例のgetUserNameMaybe.withdefaultを使うと以下のように書ける。

getUserName : Maybe String -> String
getUserName userName =
    -- Justの場合: Justの引数の文字列が返る
    -- Nothingの場合: "NO NAME"という文字列が返る
    userName
        |> Maybe.withDefault "NO NAME"

Maybe.map

Justだった時の値をmapして変換することができる。Nothingだった時は何もせずNothingをそのまま返す。

hello : Maybe String -> Maybe String
hello userName =
    userName
        |> Maybe.map (\name -> name ++ "さんこんにちは!")

result =
    Debug.log "result"
        ( hello (Just "yuna")
        , hello Nothing
        )
    -- result: (Just "yunaさんこんにちは!",Nothing)

Maybe.map2, map3,...

複数要素が全てJustの場合のみ値をmapして変換することができる。複数要素のうち1つでもNothingであればNothingを返す。

introduce : Maybe String -> Maybe Int -> Maybe String
introduce name age =
    Maybe.map2 introduceStr name age

introduceStr : String -> Int -> String
introduceStr name age =
    "名前は" ++ name ++ "です。" ++ (String.fromInt age) ++ "歳です。"

result =
    Debug.log "result"
        ( introduce (Just "yuna") (Just 22)
        , introduce Nothing (Just 22)
        , introduce (Just "yuna") Nothing
        )
    -- result: (Just "名前はyunaです。22歳です。",Nothing,Nothing)

Maybe.withdefaultとMaybe.mapを組み合わせる

冗長になりがちなcase文を使わずにMaybeを扱うことができる。

case文を用いた書き方

hello : Maybe String -> String
hello userName =
    case userName of
        Just name ->
            name ++ "さんこんにちは!"

        Nothing ->
            "無効なユーザーです"

Maybe.withdefaultとMaybe.mapを組み合わせた書き方

hello : Maybe String -> String
hello userName =
    userName
        |> Maybe.map (\name -> name ++ "さんこんにちは!")
        |> Maybe.withDefault "無効なユーザーです"

上記の例は説明のための例であまり実用性がないので、アプリケーション開発で実際に使えそうな例を2つ示しておく。

例1)名前がある場合のみ画面に名前を表示する

viewUserName : Maybe String -> Html msg
viewUserName userName =
    userName
        |> Maybe.map (\name -> div [] [text <| "user: " ++ name])
        |> Maybe.withDefault (text "")

例2)名前がある場合のみ年齢を取得するためのコマンドを発行する

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GetAge ->
            let
                cmd =
                    model.userName
                        |> Maybe.map (\name -> getAge name)
                        |> Maybe.withDefault (Cmd.none)
            in
            ( model, cmd )

port getAge : String -> Cmd msg

変数の命名

Maybeの変数には変数名の頭に「maybe」とつけると、Maybe型であることがわかりやすいのでオススメ。

hello : Maybe String -> String
hello maybeUserName =
    maybeUserName  -- ここはMaybe型
        |> Maybe.map (\name -> name ++ "さんこんにちは!")  -- ここでのnameはJustの値
        |> Maybe.withDefault "無効なユーザーです"

Maybeの多用に要注意

実際のアプリケーション開発では、例えばユーザがログインしている/いない、データを取得できている/いないなど、様々な場面でMaybeが活躍する。
Maybeは非常に便利だが、便利ゆえに大きいアプリケーションを作っていると、Maybeがどんどん増えてMaybe地獄になる。modelが持っているデータがほとんどMaybe型なんてことも...。
安易にMaybe型を使わず、よりわかりやすい表現がないか考えてみよう。

パターン1)ユーザがログインしている/いない

-- Maybeを使った場合
currentUser : Maybe User
    
-- CustomTypeを使ってログインしている/いないを表現
type CurrentUser
    = LoggedIn User
    | NotLogin

currentUser : CurrentUser

パターン2)データを取得できている/いない

-- Maybeを使った場合
users : Maybe (List User)
    
-- CustomTypeを使ってユーザ一覧情報の取得ができている/いないを表現
type Users
    = Loaded (List User)
    | NotLoaded

users : Users

上記の例を見て、「別に2パターンしかないならMaybeでよくね?いちいち型作るのメンドクセーじゃん!」と思うひともいるかもしれない。
しかし、Maybeは「ある/ない」しか表現できない。その「ある/ない」が、ログインできていないからないのか、ロード中だからないのか、といった「ある/ない」のコンテキストを失った型表現になっている。安易にMaybeを使わないメリットは、最初に実装した人がどういうコンテキストでその型を作ったかが明確になるというところにある。

例えば

users : Maybe (List User)

だと、usersがある時ってどういう状態のとき?ない時ってどういう状態のとき?ということが定義だけからは読み取ることができず、仕様やコード全体からそのコンテキストをくみ取らなければいけなくなる。これが

type Users
    = Loaded (List User)
    | NotLoaded

と書かれていると、「あ〜ロード中だからデータがないのか、なるほど」と他の実装者にもすぐに理解されるだろう。
もちろんコンテキストが自明な場合もあるので、私が言いたいのは「Maybeを使うな」ということではなく、「思考停止で安易にMaybeを使うのはやめよう!」ということです!

26
8
1

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
26
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?