4
2

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

ElmのTasksについて

Posted at

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を組み合わせることができる。

ということになります。

4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?