学習の素材は、
すごいHaskellたのしく学ぼう!
この表記は、私見・感想です。
newtype キーワードと似たようなキーワードとの比較
newtype
newtype キーワード は既存の型を包んで新しい型を作るためのもの。値コンストラクタ、フィールドがそれぞれ一つだけ用意されている。後述するようなインスタンスを作るために使用することがある。
newtype で型を包むと(ほどくと)、data と違ってオーバーヘッドがかからない点がメリット。
data
data キーワード は自作の新しいデータ型を作るためにある。値コンストラクタもフィールドも複数用意できる。 オリジナルの型を作る!ということなら、このキーワードが適切。
type
型シノニムを作るために type キーワードを用いる。既存の型に別名をつけて、プログラムを整理しやすくする。その型をどういう目的で使っているのか、コードを読む人に伝えられる。
たとえば、
Intを特定の文脈に合わせてBooksと読んでみたり。
newtype を使って型クラスのインスタンスを作る
ある型を型クラスのインスタンスにしたいをのだが、型引数が一致しなくてできないということがある。たとえば、タプル (,) :: a -> b -> (a, b) をFunctor のインスタンスにして、fmap の引数でとる関数を fst に適用したいというようなケース。
((,) a)をインスタンスにすることは簡単にできるが、これだと関数適用できるのはsndの方になってしまう。
このような時に newtype で新しい型を作り、要求を満たすことができる。
newtype Pair b a = Pair { getPair :: (a, b)} deriving (Show)
instance Functor (Pair c) where
fmap f (Pair (x, y)) = Pair (f x, y)
型引数の順序が反転しているため、固定されているのが snd の型になっていることがわかる。パターンマッチも使えるので、pair 型からタプルを取り出して、fst 側に関数適用できている。
また、getPair により、タプルに変換することも簡単に行える。
内部的にはタプルを使うが、型引数の順番だけ工夫して新しい型を作り、型クラスのインスタンスに対応させている。そういうニーズで、
newtypeが使える。
newtype と遅延評価
newtype で新しい型を作ると、必ずコンストラクタが一つ、フィールドが一つとなる ことが明らかなので、 パターンマッチにおいて引数を評価しなくてもその型であると判定できる。そのため、ある関数が newtype で作られた型を引数に取るとき、それが undefined だとしても結果がエラーとならない場合がある。 Haskell は本当に必要になるまで評価を遅らせる性質があり、ここでもその性質が現れている。
newtype CoolBool = CoolBool {getCoolBool :: String}
helloMe :: CoolBool -> String
helloMe (CoolBool _) = "hello"
この例では、GCHi で次のように書いても、結果を得られる。
*Main> helloMe undefined
"hello"
undefined :: aは任意の型として取り扱うことができる。そして、評価するとエラーになるという特徴がある。つまり、このコードにundefinedを埋め込むと、通常、コンパイルは通るものの実行する(ために評価する)とエラーを出す。上のケースではコンパイルが通り、かつ遅延評価なので評価されずに(エラーが出ないので)正常終了するということである。