Elmは基本的に作りがシンプルで理解に詰まることが少ない言語ですが、それでもやはりプログラミング言語である以上多少なりとも壁は存在します。そのうちの一つがTaskになります。Taskはいろんな説明ができてしまいますが(非同期を扱うモジュール・および型、Effect Managerを間接的に操作するための機構)、Elmの場合せっかくThe Elm Architectureと言うわかりやすい機構があるので、Elm Runtimeに処理をお願いできるもの。ぐらいの認識で十分です。詳しく原理がわからないと落ち着かんと言う人は、基礎からわかるElmをどうぞ。
TaskはCmdを作るもの
TaskはいろんなことができるのですがElmの素晴らしくシンプルでわかりやすいこととして、どれだけTaskをいじくり回しても必ずCmd msgにしなければなりません。Taskを使う上で避け得られない関数が、performとattemptの二つです。TaskはElmランタイムつまりJavaScriptに命令を出しますが、Elmは自分以外を信用しない臆病な生き物なので、それは成功するかもしれないし失敗するかもしれないと思って動きます。そんなことを言っても成功するでしょう、もし仮に失敗したとしても失敗を検知しませんよ。と言うのがperformです。わかりました失敗に備えてエラーのハンドリングもしっかりします。と言うのがattemptになります。いずれもmsgが受け取る値がa
型になります。Elmの型システム上、絶対起きえないと言うことでも明記しておかないと怒られてしまうので、そんな時コンパイラを黙らせる型がNever型になります。エラーハンドリングを扱う場合には、Result型を使います。エラーは処理に依存するので固変数x
となっています。
perform : (a -> msg) -> Task Never a -> Cmd msg
attempt : (Result x a -> msg) -> Task x a -> Cmd msg
performでHello World
それでは、TaskでHello Worldをしてみましょう。デモとコードはこちら。これは単なるStringをランタイムから受け取るためのプログラムです。ランタイムから受け取るため基本的に中身はブラックボックスなのですが、固定値であればElm側から指定することができます。必ず成功する固定値を返して欲しい場合には、Task.suceedを利用します。これは誰が見ても必ず成功するだろうと言うことで、performを利用して失敗は無視します。
type alias Model =
String
init : () -> ( Model, Cmd Msg )
init _ =
( ""
, Task.perform GotGreet <| Task.succeed "Hello World"
)
-- UPDATE
type Msg
= GotGreet String
update : Msg -> Model -> ( Model, Cmd Msg )
update msg _ =
case msg of
GotGreet greet ->
( greet, Cmd.none )
performを使って処理すべきTaskを返すものとしては、Time.now・Time.here・Random.generate・Process.sleepがあります。sleepはアニメーションなどを扱う場合に重宝するでしょう。
attemptでHello World
続いて、失敗するかもしれないことを考慮するattemptを利用してみます。デモとコードはこちら。succeedを利用している限り必ず成功してしまうため、必ず失敗をするTask.failを使います。もし、起こり得るエラーが複数種類ある場合やエラーであることを明示するためにはカスタムタイプでError型を用意して使うのが好ましいです。
type alias Model =
{ ok : String
, error : String
}
type Error
= SomeError String
init : () -> ( Model, Cmd Msg )
init _ =
( { ok = "", error = "" }
, Task.attempt GotResult <| Task.fail <| SomeError "なんか失敗しました"
)
-- UPDATE
type Msg
= GotResult (Result Error String)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotResult result ->
case result of
Ok v ->
( { model | ok = v }, Cmd.none )
Err err ->
case err of
SomeError ev ->
( { model | error = ev }, Cmd.none )
attemptを使って処理すべきTaskを返すものとしては、Http.get(post, request)・Browser.focus・Browser.blurなどがあります。Httpの場合は、BadRequestやNotFound、InternalServerErrorなどをハンドリングしたり、Browserなどの場合は、要素が見つからなかった(NotFound)時のハンドリングを要求されます。
ElmでHttpをわかってしまおうと言う別途記事でHttpやTaskの応用的な扱い方を紹介しています。良ければご覧ください。
まとめ
本当はTask.mapやandThenも紹介しようと思ったのですが、一番の混乱する理由は最終的にCmdに落とされると言う点とperform, attempt何故2種類あるの?どう扱うの? と言う点で、あとは具体的な関数のリファレンスに使い方が記されているケースがほとんどなので、これ以上深入りはしません。Taskであまり混乱しないようにするポイントはTaskとはなんなのか?と考えすぎない点とTaskの使い方自体はあまり追うのではなく、Taskを返す関数の使い方をしっかり読む、TEAやResult型などの基本をしっかり理解することだと思います。それでは、良いElmライフを!