Runner in the High

技術のことをかくこころみ

Maybeに代えてカスタム型を使う

 ElmのMaybeはデータの有無を型で表現できるゆえ非常に便利なものであるが、文脈が失われるため無闇に使い過ぎるとワケがわからなくなる。ケースによっては、カスタム型を使うことによって型でデータの有無を Maybe に代えて表現するほうがよりメンテナブルにある可能性がある。

前提

 ユーザーログインの機能を持つTODOアプリを開発している。

 上記の仕様から、おそらくモデルに currentUser: User みたいなフィールドと TODO を表す todos: List Todo があるだろうと言うことは容易に想像できる。 だが、実際には「ログインしていない」と「ログインしている」のふたつのコンテキストを表現しなければいけないため、両方とも Maybe になる。ログインしていなければ両方共 Nothing になる。

type alias Model =
    { currentUser: Maybe User
    , todos: Maybe List Todo
    }

 正直ここの Maybe は苦しい。なぜならこの Maybe は文脈を持っていないからだ。

 例えば、ログインに失敗して Nothing なのか、純粋にまだ TODO を一個も持っていないから Nothing なのかは、ここでは分からない。 ユーザーがログインに失敗したから Nothing なのか、まだログインしていないから Nothing なのかも、判断が難しい。場当たり的な処置として Result 型を使う手もある。だが、結局文脈をもたない Maybe はコードを書く側が頑張ってその文脈をコード・リーディングしながら紐解いていくことになる。これはまず大変だし、バグを生む可能性がある。

 ではログインにまつわる文脈を持たせましょう、ということで LogInStatus 型のデータを与えると以下のようになる。

type alias Model =
    { currentUser: Maybe User
    , todos: Maybe List Todo
    , logInStatus: LogInStatus
    }


type LogInStatus
    = NotLoggedIn | LoggingIn | LogInSucceeded | LogInFailed

 これでモデルにログイン文脈のデータが与えられたが、それでもまだ苦しい。

 それぞれの Maybe の結果は LogInStatus と独立しているため、開発者のうちひとりが LogInSucceeded の状態であるにも関わらず currentUser を Nothing のままにしているなどのコードを書いてしまう可能性がある。ログインが成功しているのにユーザーがいない、というのはまず仕様としてもおかしい。

カスタム型で仕様を表現する

 Elmは型付言語なので、仕様を表現するような型制約を設けるのがよい

 たとえば、以下のようにデータが前提としてログインの状態に紐づくようにすることによって Maybe を使わずに currentUsertodos の存在有無を表現できる

type alias UserData =
    { currentUser: User
    , todos: List Todo
    }

type Model
    = NotLoggedIn
    | LoggingIn
    | LogInSucceeded UserData
    | LogInFailed

 このコードであれば、まずログインが成功していない限りユーザーも、TODOも存在していないということが型レベルで表現されるため、もとのコードにあったような Maybe の結果とログイン状態が食い違ってしまうようなコードを書いてしまうのを防ぐことができる。

基礎からわかる Elm

基礎からわかる Elm

  • 作者:鳥居 陽介
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2019/02/27
  • メディア: 単行本(ソフトカバー)