学習の素材は、
すごい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
を埋め込むと、通常、コンパイルは通るものの実行する(ために評価する)とエラーを出す。上のケースではコンパイルが通り、かつ遅延評価なので評価されずに(エラーが出ないので)正常終了するということである。