Rust の turbofish と GHC 8 の Type Application ― または我々は如何にして多相な関数を単相化するか

Rust には std::str::FromStr という trait があって,データ型がこれを実装すると,from_str という名前の associated function *1 を通じて,str からそのデータ型に変換できるようになる.

use std::str::FromStr;

fn main() {
    let x = i32::from_str("42");
    println!("{}", x.unwrap()) // 42
}

これだけ見れば,特に取り立てて議論するべき点はない.

一方.strparse というメソッドを持っていて,文字通り文字列のパーズを行うのだが,以下のようなシグネチャをしている.

fn parse<F>(&self) -> Result<F, F::Err> 
where F: FromStr

str である自身を受け取って,Result<F, F::Err> 型を返す.ただし,FFromStr trait を実装している*2,といったところだ.前述した std::str::FromStr::from_str と同じことをしているが,いわば見る視点が逆転しているのである.つまり,std::str::FromStr::from_str は,Self から str を,std::str::parse は,str から F を,それぞれ眺めている.

さて,前者は str に視点を定めればよいのは明らかだが,F は多相なので,立場が逆になるとうまくいかない.どこを見ればよいかわからないからだ.具体例を挙げると,当然ながら次のコードはコンパイルできない.

fn main() {
    let x = "42".parse();
    println!("{}", x.unwrap())
}

/*
error[E0284]: type annotations required: cannot resolve `<_ as std::str::FromStr>::Err == _`
 --> /var/folders/5h/7wt7yl7n24v72zsz77_3w_w00000gn/T/vKY0lB7/51.rs:2:18
  |
2 |     let x = "42".parse();
  |                  ^^^^^
*/

エラーメッセージに従って,型注釈を与えてやれば,コンパイルに成功する.

use std::num::ParseIntError;

fn main() {
    let x: Result<i32, ParseIntError> = "42".parse();
    println!("{}", x.unwrap()) // 42
}

これだけ見ると,単に煩わしさしか感じないのだが,Rust がおもしろいのは,多相である parse メソッドに,変換先の型の情報を渡して,型を限定する文法を提供している部分にある.これは turbofish と呼ばれ ::<> という形をしている.

fn main() {
    let x = "42".parse::<i32>();
    println!("{}", x.unwrap()) // 42
}

関数の型を直接指定しているのではなく,関数に型を,あたかも引数のように与えているという点に注目してほしい.これは Λ で抽象化された型 Fi32 という具体型を渡して,関数全体の型を決定するという操作に相当しているのだと思う*3

さて,Haskell にこのような文法はなかったかと考えたが,先日 Haskell Day 2016 に赴いた際に,SPJ が System F の話をしていて,GHC 8.0 から,Type Application という機能が導入された*4と話していたことを思い出した.これを用いると,Haskell でも以下のような書き方ができる.

{-# LANGUAGE TypeApplications #-}

import Text.Read (readEither)

unwrap :: Either a b -> b
unwrap = either undefined id

main = print $ unwrap (readEither @Int "42") -- 42

Rust の Result に対応して,Either を用いた.

ghci を使えば,多相な関数に型を適用して,単相な関数にする過程を実際に確かめることができる.

$ ghci
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /Users/Ryota/.ghci
Prelude> :set -XTypeApplications
Prelude> :t read
read :: Read a => String -> a
Prelude> :t read "42"
read "42" :: Read a => a
Prelude> read "42"
*** Exception: Prelude.read: no parse
Prelude> :t read @Int
read @Int :: String -> Int
Prelude> :t read @Int "42"
read @Int "42" :: Int
Prelude> read @Int "42"
42

Hindley-Milner 型システムは強力であり,プログラマが直接型を明示しなければコンパイルできない,といった状況はそう多くない.しかしながら,幾つか例外があり,そのような場合に値ないし関数に,完全な形で注釈を与えることは,しばしば煩わしい作業である.let x: Result<i32, ParseIntError>(read :: String -> Int) 42 などと書かなくて済むように,このような仕組みを言語(または処理系)が提供してくれることは心強い.

以上の内容は,rustc 1.12.0 および ghc 8.0.1 での挙動に基づいている.

*1:引数として self を取らないもの.他の言語で言う static method や class method などに相当する.

*2:個人的にはこの where は,少なくとも初学者にとっては,Haskell における => よりもわかりやすいと思っている.Rust の影響を色濃く受ける Swift でも採用されている.

*3:この辺は全然詳しくないので,間違ってたらあとでこっそり教えてほしい.

*4:実際には,以前から内部的に実装されていたものを,一部使えるようにしたらしい.

ぷろみすのはなし

resolve.js

Promise.resolve({
  ok: true,
  json: () => Promise.resolve({})
})
  .then(res => res.ok ? Promise.resolve(res.json()) : Promise.reject(res.json()))
  .then(console.log.bind(console))
  .catch(console.error.bind(console))

reject.js

Promise.resolve({
  ok: false,
  json: () => Promise.resolve({})
})
  .then(res => res.ok ? Promise.resolve(res.json()) : Promise.reject(res.json()))
  .then(console.log.bind(console))
  .catch(console.error.bind(console))

$ diff resolve.js reject.js

2c2
<   ok: true,
---
>   ok: false,
$ node resolve.js
{}

$ node reject.js
Promise { {} }

なめとんか.

id:hiroqn に話した

  • 「それは解釈の違いで〜」
  • 「気に入らんのやったら『ぼくのかんがえた最強の Promise』作ればいいんすよ」

やっぱり

ぼくはじゃばすくりぷとがキライです!!!!!

雑感

Promise.resolvePromise.reject の間に対称性がないのがキモいと直感的に感じた,いう話なのだけれど,よくよく考えだすと,そもそもそこに対称性があってしかるべきなのかであるとか,段々とわからなくなってきた.もう僕には何もわからない.

ないんたんのパーザを書いた

京都に住んでいた頃は,ズバリ大学付近の予想降水量をないんたんで知ることができたが,ないんたん天気予報@東京 で提供されているのは,本郷・駒場・大岡山・六本木ヒルズの4ヶ所なので,以前に比べると自分の行動範囲からは遠のいてしまった.が,普段は目黒区内を行動しているので,「駒場と大岡山を足して2で割れば,それっぽい予測が得られるのでは?」という雑なアイディアを思い付いたので,やってみたくなった.

しかしながら,ないんたんのデータセットは公開されているわけではなく,JavaScript のファイルを見ても,以下のような形式の記述がなされているだけで,非常に扱いにくい.

function update_kyoto_yoshida(data) {
    data.addRows(151);
    data.setValue(0, 0, '09月04日 23時58分');
    data.setValue(0, 1, 0);
    data.setValue(0, 2, 0);
    data.setValue(0, 3, 0);
    data.setValue(0, 4, 0);
    data.setValue(0, 5, 0);
    data.setValue(0, 6, 0);
    data.setValue(0, 7, 0);
    data.setValue(1, 0, '09月04日 23時59分');
    data.setValue(1, 1, 0);
    data.setValue(1, 2, 0);
    data.setValue(1, 3, 0);
    data.setValue(1, 4, 0);
    data.setValue(1, 5, 0);
    data.setValue(1, 6, 0);
    data.setValue(1, 7, 0);
    // ...
    data.setValue(150, 0, '09月05日 02時28分');
    data.setValue(150, 1, 82);
    data.setValue(150, 2, 26);
    data.setValue(150, 3, 8);
    data.setValue(150, 4, 1);
    data.setValue(150, 5, 0);
    data.setValue(150, 6, 0);
    data.setValue(150, 7, 0);
}

そこで,パーザを書く練習がてら,上記のデータのパーザを書くことにした.

ソースコードはこちら. github.com

attoparsecmegaparsec の存在は知っていたが,パーザを書くのは初めてなので,素直に parsec を使うことにした,特に,attoparsec はエラーメッセージがわかりにくいと聞いていたので,今回は意図的に避けた.

続きを読む

wercker v2 で Rails + RSpec のビルドを高速化する

TL; DR

  • 予め作っておいた Docker イメージを box として指定する
  • rake assets:precompile で生成されたファイルをキャッシュする
  • test-queueRSpec の実行を並列化する
  • Database Cleaner の代わりに DatabaseRewinder を使う
続きを読む

Haskell でマルコフ連鎖を用いたツイートの自動生成

元ネタ

morishin.hatenablog.com

やったこと

これまでの Twitter 上の発言をもとに,マルコフ連鎖で文章を生成して,それを垂れ流す bot がいるとおもしろいかなと思って作った.しゅうまい君 (@shuumai) みたいなものなのだけど,ソースが特定の人物のものに限定されるという点で異なる.

Twitter からダウンロードできる archive に含まれる tweets.csv を入力として,MeCab形態素解析を行い,マルコフ連鎖で文章を適当に生成して,それを Twitter に投稿する.詳細な実装についてはもりしんの記事を参照のこと.

「おもしろいかな」と思った経緯はまったく覚えてないけど,たぶん inspired by @taketo958 とか @morishin_retro だと思う.

twitter.com

twitter.com

Twitter アカウントはこちら.

twitter.com

リポジトリはこちら.

github.com

続きを読む