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を返すだけのサーバーを作りました。

```js
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();
}

```

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

```json
"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"
}
```

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

```hs
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

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

```

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

### Task型 

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

```hs
helloWorldTask : Task Http.Error String
```

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

```hs 
type Task x a
```

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

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

```hs 
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**です。

```hs 
port helloWorldRunner : Task String String
```

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

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

### 成功時と失敗時の処理

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

```hs
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を、ログ表示する関数を書いています。

```hs
    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を用いて、成功時と失敗時にそれぞれ適当なメッセージをログに残すようにしています。


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


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

### Taskの基本的な概要まとめ

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

## Taskの合成

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

### コード例の紹介

```hs
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**が出てきていますが、これは後日また改めて記事にしたいと思っています。

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

```hs 
sendMe : String -> Task x ()

sendMeError : Http.Error -> Task x ()
```

それらを`port helloWorldRunner`にて、`andThen`と`onError`を用い合成していまうす。

```hs

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

ということになります。