25
12

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 3 years have passed since last update.

ElmAdvent Calendar 2019

Day 2

elm-form-decoderでユーザ入力に型付けたらelm-graphqlでドーン

Last updated at Posted at 2019-12-01

アドカレ記事みんな頑張りすぎ問題に共感しているので、できるだけ短くしたい。

道具立て

arowM/elm-form-decoderは、HTML formからのユーザ入力に、

  • アプリケーションの要請に基づくValidationを行い、
  • かつ、適切な型を与える、

優れた抽象化です。

また、dillonkearns/elm-graphqlは、SelectionSetという型を中心とするAPIで、

  • サーバ側のexposeするGraphQLスキーマに沿ったElm側moduleを自動生成した上で、
  • GraphQLの強みである操作(operation)の組み合わせを容易にする、

よく練られたパッケージです(現在ElmでGraphQLを扱うなら、もっとも有力視されていると言っていいかと)。

どちらもJson.Decoderのような抽象化にある程度慣れていれば、そこから直観的にステップアップできるでしょう。

両者の日本語解説・紹介記事をいくつかリンク:

FormをdecodeしたらSelectionSetにぶちこめばいっちょ上がりなんだよなあ

elm-form-decoderではFormのような、適当に用意したレコードなどにまずユーザ入力を文字列1で受けておき、それを適切な型のついたElmの値に"Decode"するワークフローになっています。そのための手続きを格納しているのがForm.Decoderです。

Form.Decoder input error output

そういうわけですから、入力の型、だめだったときのエラーの型、うまく行ったときの出力の型が必要になるわけですね。

ユーザ入力
 ↓
Form
 |
 | (Form.Decoder Form error output)
 ↓
output

一方elm-graphqlでは、サーバのスキーマを元にoperation(queryやmutation)が関数としてコード生成されます。それらの関数はSelectionSet型を持ちます。SelectionSet型は、

  • 組み合わせ・合成(compose)できるようになっている
  • Composeしたものを最終的に送信APIに投入すれば、サーバへの送信とエラー検知はよしなにやってくれる2
  • あるoperationのために必要な引数の情報を格納する
  • さらに、operationが成功したときのレスポンスを最終的にどのようなElm型で受けるか、という情報も保持する、

といった仕組みになっています。

SelectionSet decodesTo typeLock

(typeLockは若干わかりにくいですが、operationの種類を示すRootQuery, RootMutation, RootSubscriptionのどれかや、codecでの変換元データ型が入ります)

フォームから値を受け取ってなにかするのはたいていmutationで、mutationには引数(arguments)が必要であり、レスポンスの型共々スキーマに定められています。

elm-graphqlはスキーマを元に以下のような型や関数を生成します。

-- 生成コード

type alias MyMutationRequiredArguments =
    { value : String
    }


myMutation :
    MyMutationRequiredArguments
    -> SelectionSet decodesTo AutoGenerated.Object.ResponseObject
    -> SelectionSet decodesTo RootMutation
-- 実装は複雑なので略

つまり、もっとも単純な使い方としては、Formのユーザ入力をMyMutationRequiredArgumentsにdecodeして、myMutation関数でSelectionSetにつなぎこめばいいわけです。

このとき、スキーマに書いてあるレスポンスの型(例ではResponseObject)を、その後使用するElmの型に変換するSelectionSetも与えます。

ユーザ入力
 ↓
Form
 |
 | (Form.Decoder Form error MyMutationRequiredArguments)
 ↓
MyMutationRequiredArguments
 |
 | (myMutation)
 | (SelectionSet AtodeTsukauKata ResponseObject)
 ↓
SelectionSet AtodeTsukauKata RootMutation

こいつをelm-graphqlの送信APIにドーンぶちこめばResult error AtodeTsukauKataになって返ってくっから!じゃ、あとはよろしく!あばよ!

型で守られたぬるま湯で純粋培養されて生きていたい

elm-graphqlの生成する型は(バグがない限りは)スキーマと一致しているので、3

ユーザ入力
 ↓
Form                                     -┐
 ↓                                        |
MyMutationRequiredArguments               |     ∧∧∧∧∧∧∧∧
 ↓                                        |-- < 型で守られてる!>
SelectionSet AtodeTsukauKata RootMutation |     ∨∨∨∨∨∨∨∨
                                          |
GraphQL schema                           -┘

もし、サーバ側のAPIもGraphQL Schemaからある程度自動生成する実装方法で作られていれば、

ユーザ入力
 ↓
Form                                     -┐
 ↓                                        |
MyMutationRequiredArguments               |     ∧∧∧∧∧∧∧∧
 ↓                                        |-- < 型で守られてる!>
SelectionSet AtodeTsukauKata RootMutation |     ∨∨∨∨∨∨∨∨
 ↓                                        |
GraphQL API (GraphQL schema)             -┘

何ならサーバの実装言語が静的型付きならDBまでずっと型で守られます。うれC

真面目な話、Form.DecoderSelectionSetもよく考えられていて、特にcomposabilityが高くかつ迷いづらい(型パズルやってりゃだいたいやりたいことが完成する)ので、Elmでサービス開発する喜びが加速すると思います。もっと詳しく、という方はコメントやTwitterで質問お答えします。(アドカレ時期ですので、他にも記事出てくるかも)

では良いElmライフを!

  1. type="checkbox"のみBoolでもユーザ入力を保持可能

  2. もちろん、自分のアプリ用に更に使いやすくラップしたAPIを適宜用意するのもアリ。

  3. ちなみに今はintrospectionクエリをAPIに実際に送って生成するようになっていますが、静的なスキーマファイルから生成するオプションも提案されてたので入りそうな気配はある

25
12
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
25
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?