この記事は以下のページに移転しました.
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 }
これだけ見れば,特に取り立てて議論するべき点はない.
一方.str は parse というメソッドを持っていて,文字通り文字列のパーズを行うのだが,以下のようなシグネチャをしている.
fn parse<F>(&self) -> Result<F, F::Err> where F: FromStr
str である自身を受け取って,Result<F, F::Err> 型を返す―ただし,F は FromStr trait を実装している*2―といったところだ.前述した
std::str::FromStr::from_str と同じことをしているが,いわば見る視点が逆転しているのである.つまり,std::str::FromStr::from_str は,Self から str を,std::str::parse は,str から F を,それぞれ眺めている.
さて,前者は str に視点を定めればよいのは明らかだが,F は多相なので,立場が逆になるとうまくいかない.どこを見ればよいかわからないからだ.具体例を挙げると,当然ながら次のコードはコンパイルできない."42" という文字列をどの型の値としてパーズしたいかがわからないのである.
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 }
i32 のパーズに失敗した際のエラーは ParseIntError だと分かりきっているので,これだけ見ると単に煩わしさしか感じないのだが,Rust がおもしろいのは,多相である parse メソッドに,変換先の型の情報を渡して,型を限定する文法を提供している部分にある.これは turbofish と呼ばれ ::<> という形をしている.先程のコードを turbofish を使って書き直す場合,parse メソッドとメソッド呼び出しの () の間に ::<i32> と書いてやる.
fn main() { let x = "42".parse::<i32>(); println!("{}", x.unwrap()) // 42 }
関数の型 (&str -> Result<i32, ParseIntError>) を直接指定しているのではなく,関数に型 i32 を,あたかも引数のように与えているという点に注目してほしい.これは Λ で抽象化された型 F に i32 という具体型を渡して,関数全体の型を決定するという操作に相当しているのだと思う*3.
さて,Haskell にこのような文法はなかったかと考えたが,先日 Haskell Day 2016 に赴いた際に,SPJ が System F の話をしていて,GHC 8.0 から,Type Application という機能が導入された*4と話していたことを思い出した.これを用いると,Haskell でも以下のように @Int という記法で,多相な関数に Int 型を適用して単相化することができる.
{-# 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 での挙動に基づいている.