search
LoginSignup
2

More than 5 years have passed since last update.

posted at

ElmのTasksについて

ElmのSignalについての記事は沢山上がっていますので、今回はTaskについての記事を書きます。

Tasksについて

非同期処理とGUI

Elmには、非同期処理を上手く扱う仕組みとしてTasksというものがあります。ElmはフロントエンドGUIのための言語でありますが、GUIでは非同期処理は欠かすことができないでしょう。とある、重い処理のためにユーザの操作ができないとあれば、困る場合があるからです。

さて、非同期処理は基本的にコールバック地獄になりやすく、そのためJSではPromiseなど、さまざま言語でコールバック地獄をなんとかしようとする仕組みがあります。ElmのTaskもそうした、仕組みの一つでしょう。

Tasks

さて、Tasksは、Elm 0.15から導入されました。基本的には、HTTPのリクエストや、データベースを扱いたい時などの、非同期処理 を扱うための仕組みです。

簡単なソースコード例の紹介

いろいろと説明する前に、Tasksを使ったソースコードを出してみましょう。

Httpの通信するサンプルを書いてみます。elmを書く前に、nodeで簡単なjsonを返すだけのサーバーを作りました。

var http = require('http');
var server = http.createServer();

server.on('request', doRequest);
server.listen(8080);
function doRequest(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('{"msg":"Hello World"}');
    res.end();
}

さて、このサーバーにリクエストを送るサンプルを書きます。依存するライブラリは以下の通りです。

"dependencies": {
        "elm-lang/core": "2.1.0 <= v < 3.0.0",
        "evancz/elm-html": "4.0.2 <= v < 5.0.0",
        "evancz/elm-http": "3.0.0 <= v < 4.0.0"
}

さて、以下が簡単なサンプルになります。

import Http 
import Html exposing (div, text)
import Html.Attributes
import Json.Decode as D exposing ((:=))
import Task exposing (..)
import Debug exposing (log)


main =
   div [] [text "Hello World"]

helloWorldTask : Task Http.Error String
helloWorldTask =
  Http.get (D.object1 identity ("msg" := D.string)) "http://localhost:8080"

port helloWorldRunner : Task String String
port helloWorldRunner =
  let
    errorLog error = 
              log "error" <|
                  case error of
                    Http.Timeout ->
                      "timeout"

                    Http.UnexpectedPayload msg ->
                      "unexpect payload : " ++ msg

                    Http.BadResponse code msg ->
                      "bad respose, code : " ++ (toString code) ++ " msg : " ++ msg

  in
    mapError errorLog << map (log "respose") <| helloWorldTask


まず、一つずつ簡単に、解説して行きます。

main

main =
   div [] [text "Hello World"]

Taskとか関係はありませんが、とりあえず、elm-htmlで、helloworldを表示するだけのコードです。

Task型

helloWorldTaskについて見ていきましょう。まず型からです。

helloWorldTask : Task Http.Error String

Taskという型 が出てきました。Taskという型はなんらかの非同期処理の型です。
Taskは、2つの型引数を取ります。Task型は、なんらかの処理(task)を行なう型であり、その処理が失敗時(エラー時)に受け取る型と、成功時に受けとる型を想定します。なので、2つの型引数が考えられます。

type Task x a

基本的に、xはエラー時の値で、aは成功時の値を示します。なので、helloWorldTaskは、エラーとして、Http.Error型の値、成功時にString型の値を扱う、Task型となります。

helloWorldTaskの実装を見てみましょう。

helloWorldTask =
  Http.get (D.object1 identity ("msg" := D.string)) "http://localhost:8080"

Http.getは、getメソッドのリクエストを送る関数です。第一引数にレスポンスをDecodeための、Decoder型を、そして、リクエストURLのString型を取ります。

処理としては、"http://localhost:8080"にgetのリクエストし、レスポンスとして{"msg":"Hello World"}という文字列が返されるため、それをDecodeする処理です。

port

さて次に, portです。

port helloWorldRunner : Task String String

先程、Taskを定義しましたが、定義しただけでは、Taskは動きません。Taskを動かすためには、portを通して定義しなければなりません。

また、 portと通して定義できる値は、TaskSignal Taskです。実行タイミングを扱うのであれば、Signal Taskを定義しましょう。

成功時と失敗時の処理

さて、helloworldRunnerの実装を見て行きましょう。

port helloWorldRunner =
  let
    errorLog error = 
              log "error" <|
                  case error of
                    Http.Timeout ->
                      "timeout"

                    Http.UnexpectedPayload msg ->
                      "unexpect payload : " ++ msg

                    Http.BadResponse code msg ->
                      "bad respose, code : " ++ (toString code) ++ " msg : " ++ msg

  in
    mapError errorLog << map (log "respose") <| helloWorldTask

さて、まずは、letを使い、errorLogという、Http.Errorを、ログ表示する関数を書いています。

    errorLog error = 
              log "error" <|
                  case error of
                    Http.Timeout ->
                      "timeout"

                    Http.UnexpectedPayload msg ->
                      "unexpect payload : " ++ msg

                    Http.BadResponse code msg ->
                      "bad respose, code : " ++ (toString code) ++ " msg : " ++ msg

次に、Task.mapErrorと、Task.mapを用いて、成功時と失敗時にそれぞれ適当なメッセージをログに残すようにしています。

    mapError errorLog << map (log "respose") <| helloWorldTask

<<などを使い、関数合成しています。

Taskの基本的な概要まとめ

以上が、Taskの基本的な概要となります。基本的にTask型を作り、portを通して定義することによって、Taskが動くようになります。

Taskの合成

Signal同時が合成できたように、Taskもまた、合成することができます。それを、また先程の例を修正し、説明してきます。

コード例の紹介

import Http 
import Html exposing (div, text)
import Html.Attributes
import Json.Decode as D exposing ((:=))
import Task exposing (..)
import Debug exposing (log)


main =
   Signal.map (\result -> div [] [text result]) myMailbox.signal


helloWorldTask : Task Http.Error String
helloWorldTask =
  Http.get (D.object1 identity ("msg" := D.string)) "http://localhost:8080"


myMailbox : Signal.Mailbox String
myMailbox = Signal.mailbox ""

sendMe : String -> Task x ()
sendMe = Signal.send myMailbox.address

sendMeError : Http.Error -> Task x ()
sendMeError error = 
  sendMe <| case error of
    Http.Timeout ->
      "timeout"

    Http.UnexpectedPayload msg ->
        "unexpect payload : " ++ msg

    Http.BadResponse code msg ->
      "bad respose, code : " ++ (toString code) ++ " msg : " ++ msg



port helloWorldRunner : Task x ()
port helloWorldRunner =
    helloWorldTask `andThen` sendMe
                   `onError` sendMeError

上のコードは、Mailboxが出てきていますが、これは後日また改めて記事にしたいと思っています。

さて、上記では、sendMesendMeErrorというTask x ()を返す関数を作成しています。

sendMe : String -> Task x ()

sendMeError : Http.Error -> Task x ()

それらをport helloWorldRunnerにて、andThenonErrorを用い合成していまうす。


port helloWorldRunner : Task x ()
port helloWorldRunner =
    helloWorldTask `andThen` sendMe
                   `onError` sendMeError

ここで、andThenはTaskが成功した時にするTaskを、onErrorは失敗したときにするTaskを合成しています。上記では、helloWorldTaskが成功した時に、成功時の値を受けとりsendMeで返されるTaskが実行され、エラー時に、失敗時の値を受けとりsendMeErrorで返されるTaskが実行されるのです。

まとめ

以上で、Taskを使って、elmを非同期処理を書くための概要を説明しました。

まとめますと、

  • Taskは非同期処理を行なう型。成功時と失敗時の型を持つ。
  • portを通して定義されるTaskが実行される。
  • Taskは、他のTaskを組み合わせることができる。

ということになります。

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
What you can do with signing up
2