私のためのHaskell〜 [] リストを理解する

  • リストの手前と、リストの定義
    • Haskellの「大元」というのがある
    • それをコンパイルしないと使えないので、コンパイラがあるが、GHCはそのひとつ
    • GHCコンパイラは複数のモジュールファイルを持っていて、その定義がGHCによるコンパイルで使われる
    • 基本的にはHaskellの大元にGHCコンパイラのルールを乗せたもので、プログラミングのルールが決まっていると考えてよい(ようだ)
    • このほかに、Preludeというモジュールもある。これはGHCのモジュール群にさらに追加されたもの
  • リストの型定義
data [] a = [] | a : [a]
    • これだけ
    • 何でもよいが、ある型を型変数 a で表したときに、型「リスト、記法としては 」は、空リスト ()もしくは、同じ型を要素として、連結されたもの。と書かれている。
    • このGHC.Types.hsがimportしているのはGHC.Prim.hsのみであって、それは本当に、基本的な数字とかCharとかの定義をしているもの
    • この状況での挙動を調べるには、PreludeなしでGHCiを使ってみるとよい(こちら)
ghci -XNoImplicitPrelude
> :type []
data [] a = [] | a : [a]
> :info []
data [] a = [] | a : [a]        -- Defined in ‘GHC.Types’
> :type (:)
( : ) :: a -> [a] -> [a]
> :type fmap
ERROR
  • リストに性質を加える
    • 次にPreludeをimportしなおしてGHCiを起動する(普通にGHCiを起動する)
ghci
Prelude> :info []
data [] a = [] | a : [a]        -- Defined in ‘GHC.Types’
instance Eq a => Eq [a] -- Defined in ‘GHC.Classes’
instance Monad [] -- Defined in ‘GHC.Base’
instance Functor [] -- Defined in ‘GHC.Base’
instance Ord a => Ord [a] -- Defined in ‘GHC.Classes’
instance Read a => Read [a] -- Defined in ‘GHC.Read’
instance Show a => Show [a] -- Defined in ‘GHC.Show’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Foldable [] -- Defined in ‘Data.Foldable’
instance Traversable [] -- Defined in ‘Data.Traversable’
instance Monoid [a] -- Defined in ‘GHC.Base’
    • Preludeにより、リスト [] にさまざまなことが追加されていることがわかる。
    • GHC.Types以外のGHC.hogeモジュールが読み込まれ、そこに定義されていることも読み取れる。
    • 性質は、リスト [] 型を、型クラスに登録することで与えられる。
      • 型クラスに登録する、とは、型クラスを定義し、それのinstanceとしてリスト [] を書いておくことで実現される。
    • 型クラスの性質は、型宣言の中で規定される、型クラスに帰属する型に共通して適用可能な関数によって決まる。
    • リストは、基本的な型なので、(Preludeモジュールだけでも)いろいろな型クラスに属していることが読み取れる。
    • 型クラスによる性質の付加方法に2通り
      • 上記のinstance宣言は全部で10通りあるが、2種類に分けられる。
        • Hoge [a]、と宣言しているものと、Hoge []、と宣言しているものの2種類である
      • リストを、要素と要素を入れる入れ物との2つから構成されると考えたとき、Hoge [a]で宣言されて付与される性質は、要素とその入れ物の全体としてのリストに関する性質であるのに対して、Hoge [] で宣言されて付与される性質は、入れ物としてのリストに付与される性質という違いがある(ようだ)
      • 以下には、Hoge [a]宣言している、Eq型クラスで規定される関数 (==)と、Hoge [] 宣言している、Functor型クラスで規定される関数 fmapとを比べてみたものである。
      • (==)では、リストが a として扱われているのに対して、fmapの方では、リストの入れ物がf、リストの要素が a (または b )として表されており、リストの要素の型自体は、リストでない場所 ( a -> b ) でも定義づけに使われていることからも、「入れ物」と「要素」とは別扱いが必要である。
Prelude> :type (==)
(==) :: Eq a => a -> a -> Bool
Prelude> :type fmap
fmap :: Functor f => (a -> b) -> f a -> f b