Runner in the High

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

DEVでフォームにおけるOpaque Typeの設計に関するシリーズを書いた

DEVにシリーズ機能というものがあることを知って試してみた。全2作。

dev.to

dev.to

elmbitsでも取り上げられた

内容について

全部は書けないのでざっくり。

一般的にOpaque Typeは以下のようなnew関数を公開して、バリデーションの結果Result型ないしMaybe型でカプセル化された型を返すような設計になっていることが多い。

type Name =
    Name String


new : String -> Maybe Name
new value =
    if String.length value > 100 then
        Nothing
    else
        Just (Name value)

ElmのパッケージではUrlモジュールなどがいい例で、fromString関数で受け取る文字列が正しいURLであればUrl型のデータがもらえる、みたいな設計になっている。

これはつまり、バリデーションを通らないものはアプリケーションのライフサイクル上存在できない(DDDで言うところの不変条件に近い)ものにするための設計手法だと言える。つまりJustであるものは常に有効なデータだと型で表現されている。

フロントエンドにおけるバリデーションと有効性

この考え方をフロントエンドに適用しようとすると少し難しい。なぜなら、フロントエンドは状態として「不完全」なものも受け入れなければならないことが多いからである。

ユーザーがフォームに文字列を入力している過程などは、必ず不正な値の状態から始まる。もしURLの入力を求めるフォームであれば、ユーザーがURLを入力し終えるまでは常にフォームに入っているデータの状態は不正であると言える。

これを素直にサーバーサイドと同じものとして捉えてしまうと、ユーザーの入力中のデータは不変条件を満たしていないので常に無効にする、のようなちぐはぐなロジックを生むことになる。フロントエンドでは入力中のデータも状態の一部として捉えるため、無効なデータであるとして破棄してしまうと永遠にユーザーが入力を完了することのできないフォームを作ることになってしまう。

つまり、フロントエンドではバリデーションにおける「有効」「無効」の状態以外に「入力中」のような状態が必要になる。では、これを現実的にどう設計し実装するか。これが、自分がDEVに投稿した"Designing Opaque Type for form fields in Elm"シリーズのPart 1でカバーしている内容である。

モジュール・コンポジション

Part 2ではPart 1で実装されたフォーム・モジュールの再利用性を高めるために、モジュール・コンポジションを用いた設計の導入を解説している。

f:id:IzumiSy:20200506145449p:plain
モジュール・コンポジション

Elmにはクラスの概念がないため、もちろん継承の概念もない。したがって、異なるモジュールの振る舞いを再利用するためには、特化したモジュールが抽象化したモジュールに対して処理の移譲を行うようにする。このような明示的な振る舞いの獲得を、一般的にはモジュール・コンポジションと呼ぶことが多い。

Elmにおけるモジュール分割は膾炙したノウハウがないためとっかかりが難しいが、この"Designing Opaque Type for form fields in Elm"のシリーズでは

  1. まず型に関する振る舞いを中心にモジュールを作成
  2. 同様の機能をもつモジュールから抽象的な振る舞いをさらにモジュールへ抽出

というふたつの流れに沿ってモジュール分割を行う手順を示している。

型の定義を中心としてモジュールをつくるという設計そのものはElmにおいて一般的ではあるが、さらにそこからどうモジュールを分けていくべきか、という点をモジュール・コンポジションの考え方と共に解説している。