RowToListであそぼう！（苦悶）

![PAKU6350_TP_V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64695/894f1e30-6efe-8ad9-8a14-0fd4dbea68f0.jpeg)



全世界70億人のPureScripterのみなさん、お疲れさまです。PureScriptで今一番熱い話題といえば**`RowToList`**ですが、PureScript v0.12ではいろんなライブラリにこの`RowToList`を使って各種のインスタンスが追加で定義されました。たとえば、値を文字列にして標準出力をする関数`logShow`は、今までは`logShow { x : 10.0, y : 20.0 }`みたいに直接オブジェクトを渡すことはできなかったのですが、このRowToListのお陰でそれができるようになりました。そんなのElmとかでもできるやんけと思ったElmerの方もいらっしゃるとは思いますが、PureScriptのそれは型チェックが厳格に効いていて、`logShow { x : 10.0, y : pure }`みたいにプロパティに`Show`でない型を含むようなオブジェクトを渡そうとしても、ちゃんとコンパイルエラーとして検出できます。すごい！わたしも`RowToList`やってみたい！（純粋）　

JavaScriptでは、`Object.keys`で動的にプロパティの一覧を取得したり、`Object.assign`でオブジェクト同士をマージしたりというメタい操作ができますが、同じようなメタい操作を厳格な静的型付け言語であるPureScriptでも厳格なままで記述できるようになったということです。RowToListについては、Qiita上でも[ヘンゼルさん](https://qiita.com/hansel_no_kioku/items/289d1a186b70223613c9)や[ジャスティンさん](https://qiita.com/kimagure/items/7d777826acf371293a93)が解説をしてくれているので、そちらも参考にしていただくとよいかと思います。



# 発端

ところで以前からちょっと解決したい問題があって、それがちょうどこの`RowToList`でうまく解決できそうなのです。ゲームとか作っていると、画像やオーディオといったリソースファイルを大量に読み込むことがあります。JavaScriptでいうところの`Promise`みたいな`Aff`というデータ型を使うのですが、複数個の非同期処理を並行して実行するときの構文は次のようになるかと思います。

```haskell
  results <- sequential $ { foo: _, bar: _, dir: _, baz: _  } 
      <$> parallel (readTextFile UTF8 "foo.txt") 
      <*> parallel (readdir ".")
      <*> parallel (pure 42 :: Aff Int)
      <*> parallel (readTextFile UTF8 "baz.txt")
```

ここでたとえば`"baz.txt"`というファイルの内容が`results.baz`に格納されるのですが、`baz`というプロパティ名は1行目に、`"baz.txt"`というファイル名は5行目と、この両者がすこし離れた行に別々に書かれることになるので、これが少々読み書きしづらいところがあります。Applicativeの`<$>`と`<*>`もいちいち挟まってきて、見た目もとにかくうるさいです。Applicativeに慣れてない人が見たらドン引きすると思います。慣れてる人でもちょっと引くと思います。本当はオブジェクトを使って次のように書きたいです。

```haskell
  results <- props {
      foo: readTextFile UTF8 "foo.txt",
      dir: readdir ".",
      bar: pure 42 :: Aff Int,
      baz: readTextFile UTF8 "baz.txt"
  }
```

[bluebirdには`Promise.props`というメソッドがあって](http://bluebirdjs.com/docs/api/promise.props.html)、まさにこの機能を提供しています。PureScriptも負けていられません。このpropsを実現してみたいと思います。ちなみにですが、同じくAltJSの静的型付けの言語の同志であるTypeScriptでは、この`Promise.props`をどのように型付けしているのでしょうか？　TypeScriptはSum TypeだとかString Literal Typeだとか型システムをどんどん意欲的に強化していて、期待が持てそうですよね。

```typescript:bluebird/v2/index.d.ts
/**
 * Like ``Promise.all`` but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason.
 *
 * If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties.
 *
 * *The original object is not modified.*
 */
// TODO verify this is correct
// trusted promise for object
static props(object: Promise<Object>): Promise<Object>;
// object
static props(object: Object): Promise<Object>;
```

……。ハア～～(クソデカため息)


# `RowToList`を使った`props`の型の定義

まずはこの`props`関数がどのような型を持っているか考えてみます。オブジェクト(レコード)を受け取って、`Aff`な値を返すわけですから、こんな感じでしょうか。

```haskell:擬似コード
props :: forall row row'. Record row -> Aff (Record row')
```

でもこれだとこの関数にどんなオブジェクトでも渡せてしまうし、非同期処理の結果としてどんなオブジェクトでも返せるという大嘘をついています。TypeScriptと同じガバガバ型注釈です。こんなガバ定義書いてたら静的型付け一族の面汚しです。そこで、`row`と`row'`に必要な制約を持たせてあげます。気持ち的にはこんな感じです。

```haskell:擬似コード
props :: forall row row'. 
  "rowとrow'にそれっぽい制約" 
  => Record row 
  -> Aff (Record row')
```

さて、今度はこの制約の中身を考えていきます。たとえば、`row`が`foo :: Aff String`というプロパティを持っていたら、その非同期処理の結果の値である`row'`は`foo :: String`というようなプロパティを持つことになるので、それぞれのプロパティに対して`Aff a`を`a`に取り出すというような制約を書けばいいです。このようなときに`RowToList`型クラスが役に立ちます。

型レベル計算では、型クラスとインスタンスは型レベルの関数みたいなものだと捉えるとわかりやすいかと思います。型クラスの定義が『関数の型』の定義で、インスタンスが『本体』の定義です。`RowToList`は任意の行型`# Type`を`RowList`という型に変換する関数みたいなものです。`RowList`はプロパティ名、プロパティの型を要素にもった、(もちろん型レベルの)リストです。また、`ListToRow`でその`RowList`を再び`# Type`に戻すことができます。

```haskell:擬似コード
props :: forall row row' list list'. 
  RowToList row list 
  => "listからAffを取り除き、その結果をlist'に入れる",
  => ListToRow list' row'
  => Record row 
  -> Aff (Record row')
```

型レベル計算に慣れていない人はこのあたりですでに**絶句**すると思いますが、どうか心を強く持ってください。`RowToList xs list`という制約は、『`xs`に`RowToList`を適用して、その結果を`list`に入れる』というような気持ちで読めばいいと思います。`ListToRow`も同様です。気持ち的にはそんな感じです。自分が型検査器になろうとするのではなくて、ちょっと変な構文の動的型付け言語なんだと思うのが大事です。

`"listからAffを取り除き、その結果をlist'に入れる"`という操作の部分は再帰的な操作が必要になるので、型クラスとして次のように切り出しました。

```haskell
class Extract (xs :: RowList) (ys :: RowList) | xs -> ys
instance extractNil :: Extract Nil Nil
else instance extractCons :: Extract ys zs => Extract (Cons key (Aff value) ys) (Cons key value zs)
```

冒頭の`class Extract (xs :: RowList) (ys :: RowList) | xs -> ys`のところは、`Extract`という名前の関数を定義しているみたいな気持ちで読みます。`Extract`には`xs`という`RowList`の引数があって、また結果を入れる変数は`ys :: RowList`となっています。`xs`と`ys`のどっちが入力でどっちが出力かわかるように、fundepで`xs -> ys`と書いて指定しています。

`RowList`はリストの一種なので、`Cons key (Aff value) ys`というように『パターンマッチング』を行うと先頭の要素を取り出せます。このパターンマッチングで同時に`Aff value`から`value`も取り出しました。それから残りの要素を`Extract ys zs`という制約で処理し、`Cons key value zs`というようにして結果を返します。

リストの処理なので`Nil`と`Cons`で場合分けしなければなりませんが、これは複数のインスタンスを定義することで表現します。またPureScript 0.12から入った**instance chain group**という機能で、この『パターン』がオーバーラップしている場合でも『パターンマッチング』を厳密に定義できるようになりました。あいだに挟まっている`else`キーワードがそれです。

この`Extract`が書ければ、さっきの`prop`の型注釈を埋められます。こんな感じで`list`に『適用』し、結果を`list'`に『代入』します。

```haskell 
props :: forall row row' list list'. 
  RowToList row list 
  => Extract list list'
  => ListToRow list' row'
  => Record row -> Aff (Record row')
```

天下り的にならないように、ちょっと擬似コードで回り道しながらコードを書いていく考え方をなぞってみました。ふう。


# `props`の本体の定義

さて、型が書けたところで、次は関数の本体の定義をやっていきます。配列に入った任意個の`Aff a`を平行に実行するには、`parSequence`という関数を使います。オブジェクトからそれぞれのプロパティを取り出して配列に入れ直すわけで、JavaScriptでいうところの`Object.values`のようなことをしたいのですが、PureScriptにはそれをやる関数がないので(多分)、FFIで定義することにします。

```haskell
foreign import objectToArray :: forall row heterogenous.  
  Record row -> Array (Aff heterogenous)
```

任意の`Record row`を`Array (Aff heterogenous)`に変えてしまっています。PureScriptの`Array`はhomogenousなので、これでは本来ホモしか許されない世界にヘテロが紛れ込んでいるガバガバ定義なわけですが、`props`の内部だけで使うので許してください。本体はこんな感じです。

```js:JavaScript
exports.objectToArray = function(xs){
  return Object.keys(xs).sort().map(function(key){ 
    return xs[key]
  })
}
```

また、結果の配列をオブジェクトに戻す、次のような関数も定義します。これもガバ定義ですが、どうせFFIなので気にしない、気にしない。ひとつめの引数`Record row`はもとのオブジェクトのプロパティ名を調べるために使います。

```haskell
foreign import arrayToObject :: forall row row' heterogenous. 
  Record row -> Array heterogenous -> Record row'
```

```js:JavaScript
exports.arrayToObject = function(xs){
  return function(values){
    const row = {}
    Object.keys(xs).sort().forEach(function(key, i){ row[key] = values[i] })
    return row
  }
}
```

これさえできれば、あとは`props`の本体の定義をするだけです。

```haskell 
props :: forall row row'. Extract row row' => Record row -> Aff (Record row')
props tasks = arrayToObject tasks <$> parSequence (objectToArray tasks)
```

できたー！　`props`の定義の本体ではFFIを使ってガバ操作が行われていますが、`props`を外部から見るぶんにはちゃんと厳格に型付けされています。……なんか記事だとスムーズに書いているように見えるでしょうけれど、実際にはとても苦悶しながら書いています。

# 使ってみる

```haskell 
main :: Effect Unit
main = launchAff_ do

  results <- props {
      foo: readTextFile UTF8 "foo.txt",
      dir: readdir ".",
      bar: pure 42 :: Aff Int,
      baz: readTextFile UTF8 "baz.txt"
  }

  logShow results
```

これを実行すると、それぞれの非同期処理が並列に実行されて、結果がオブジェクトの対応するプロパティに詰められて返ってきます。v0.12からレコードが`Show`のインスタンスになったので、出力も`logShow`一発です。

```
{ bar: 42, baz: "bar", dir: [".gitignore",".psc-ide-port",".pulp-cache",".purs-repl","baz.txt","bower.json","bower_components","foo.txt","node_modules","output","package-lock.json","package.json","src","test"], foo: "foo" }
```

ちなみに、プロパティの順序が変わっていることに気付いた方もいるかもしれません。`RowToList`を通すとプロパティが名前で自動的にソートされて`RowList`へと変換されます。そのほうが便利な場面も多いし、気にしない、気にしない。

もちろん、`results`の型は型推論により次のように正しく型付けされています。

```haskell
results :: { foo :: String, dir :: Array String, bar :: Int, baz :: String }
```

そのため、`results.baz`のようにすれば結果のオブジェクトの個々のプロパティを参照できますし、うっかり`results.bax`みたいにプロパティ名をタイプミスしてしまっても、ちゃんとコンパイルエラーが出ます。

```haskell 
log results.baz           -- barと出力される

log results.bax           -- コンパイルエラー！
```


# さいごに

PureScriptの中の人たちはコンパイラを小さく保つのが好きらしく、言語組み込みの特別な型や関数というものを極力減らしています。でも、もちろん必要な柔軟性や堅牢性は確保しつつも、RowToListのような汎用性のある機能だけを最低限提供して、型システムを悪用……じゃなかった**有効利用**した型レベル計算を使い、外部のライブラリのレベルで機能を拡張しています。

この種の機能を実現するためには、Template Haskellみたいな邪悪な黒魔術を駆使する方法や、Elmのように`toString : a -> String`とか動的型付けに近いゆるガバ定義にする方法もあるとは思います。それらに比べるとPureScriptの方法は、言語を小さく保ちつつも柔軟性と厳格さも確保できる、優れた方法のように思います。型レベル計算を自分で書くのはなかなかどうしてややこしいですが、誰かがすでに定義してライブラリとして提供してくれているものを使うぶんにはそれほど難しくはありません。前述のジャスティンさんが[simple-json](https://github.com/justinwoo/purescript-simple-json)や[kushiyaki](https://github.com/justinwoo/purescript-kushiyaki)のようなイケてるライブラリをいくつも公開してくれているので、ぜひ使ってみるといいと思います。それにしても型レベル計算つらい。今回書いたコードはgistに貼り付けておきました。ライブラリにするにも一発ネタすぎるので。

* https://gist.github.com/aratama/ee3ec6caa42ce3c5c984e4e6e333aeed

# 参考文献

* http://bluebirdjs.com/docs/api/promise.props.html
* https://qiita.com/kimagure/items/7d777826acf371293a93
* https://qiita.com/hansel_no_kioku/items/289d1a186b70223613c9
* https://github.com/purescript/purescript/issues/2315
* http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#toString
* https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/bluebird/v2/index.d.ts
* https://liamgoodacre.github.io/purescript/rows/records/2017/07/10/purescript-row-to-list.html