プログラミングHaskellを読みながら疑問点を書いてみる(その1)

最近、プログラミングHaskellを通勤電車の中で読んでいるのだけど、目で追っているだけでは理解できなくなってきたので手を動かしながら、それでもわからない部分はここに記録していくことにする。
わかるようになったときに、以前は何がわからないと思っていたのかを思い出すのにも役に立つだろうし。

実行環境

以前にもHaskellを少し学習したことがあり、僕のmacにはghcがインストール済み。ghciという対話的にhaskellを実行できる環境もある。
最初はとりあえずghci上コード書いてやっていこうかと思ったのだけど、ghciで関数を定義しようとしたらエラーが、、

*Main> hoge x = x + 3

<interactive>:1:7: parse error on input `='
*Main> 

うーむ、、、
本を見ていたらHugsでは:load filenameとやるとロードできると書いてあったので、ghciでもやってみたらできた。なので、当面はこのやりかた(emacsで書いてファイルに保存して、それをghciで:loadして動かす)でやっていく。これを書いている今はネットもつながってないし調べるのは後で。

sum' [] = 0
sum' (x:xs) = x + sum xs
:load sum.hs 
[1 of 1] Compiling Main             ( sum.hs, interpreted )
Ok, modules loaded: Main.
*Main> sum' [1..10]
55
*Main> 

第3章 型とクラス

1、2章は軽く飛ばして、3章から復習。

  • 型とは、互いに関連する値の集合
  • 表記は以下のように。(Falseの型はBool)
    • 例: False :: Bool

ghciで:type hogeとやると式hogeの型が表示される

*Main> :type not
not :: Bool -> Bool
*Main> 
いろいろな型
  • リスト型
    • 同じ型の要素の並び
    • '['と']'で囲む
    • 例:
      • [False, True, False] :: [Bool]
      • ['a','b','c','d'] :: [Char]
      • ["One", "Two"] :: [String]
  • タプル型
    • 有限個の要素の組
    • 各要素の型は違ってもよい(リストは同じじゃないと駄目)
    • '('と')'で囲む
    • 例:
      • (False, True) :: (Bool,Bool)
      • (False, 'a', True) :: (Bool, Char, Bool)
      • ("One", True, 'a') :: (String, Bool, Char)
    • 要素数0のタプルを「ユニット」、2のタプルを「組」と呼ぶ。要素数1のタプルは許されていない。なぜなら(1+2)*3のような括弧と区別がつかないから。
    • タプルの要素数は有限でなければならない。なぜなら評価の前に型が必ずけっていできないといけないから。
  • 関数型
    • 関数はある型の引数を他の型の結果に変換する
      • 例:
        • not :: Bool -> Bool
        • isDigit :: Char -> Bool
  • 多相型
    • lengthはリストの長さを返す。リストは整数のリストでも文字列のリストでも関数のリストでもOK
      • 例:
        • length [13,5,7]
        • length ["Yes", "No"]
        • length [isDigit, isLower, isUpper]
    • このlengthのようにどんな要素のリストにも適用できることを表すために型変数を使う
      • 型変数は小文字で始まる。通常はa, b, cなどが使われる
      • 例:
        • length :: [a] -> Int
          • 任意の型aに対して関数lengthは型「[a] -> Int」を持つ
  • 多重定義型
    • 例:
      • (+) :: Num a => a -> a -> a
        • Numクラスのインスタンスaに対してa型の引数を二つとり、a型の値を返す
基本クラス

クラスとは共通のメソッド(多重定義された関数)を提供する型の集合。haskellではクラスには値が属するのではなくて型が属する。(言い方変かな?)

  • Eq 同等クラス
    • メソッド
      • (==) :: a -> a -> Bool
      • (/=) :: a -> a -> Bool
    • Bool, Char, String, Int, Integer, FLoatといった基本型はすべてEqクラスのインスタンス
    • 文字列、リスト、タプルもEqのインスタンス
    • 関数型はEqのインスタンスではない
  • Ord 順序クラス
    • メソッド
      • (<) :: a -> a -> Bool
      • (<=) :: a -> a -> Bool
      • (>) :: a -> a -> Bool
      • (>=) :: a -> a -> Bool
      • min :: a -> a -> a
      • max :: a -> a -> a
    • Bool, Char, String, Int, Integer, FLoatといった基本型はすべてOrdクラスのインスタンス
    • 文字列、リスト、タプルもOrdのインスタンス。辞書的に順序づけられる
  • Show 表示可能クラス
    • メソッド
      • show :: a -> String
    • showメソッドを用いて文字列に変換可能な型の集合。
    • Bool, Char, String, Int, Integer, FLoatといった基本型はすべてShowクラスのインスタンス
    • リストやタプルも同様
  • Read 読込可能クラス
    • メソッド
      • read :: String -> a
    • Bool, Char, String, Int, Integer, FLoatといった基本型はすべてReadクラスのインスタンス
      • って書いてあるけど、String型もReadクラスのインスタンスなの?試すと失敗するんだけど、、
Prelude> read ""abc"" :: String

<interactive>:1:7: Not in scope: `abc'
Prelude> 
        • と思ったらエスケープが必要だった
Prelude> read "\"abc\"" :: String
"abc"
Prelude> 
    • リストやタプルも同様
  • Num 数値クラス
    • NumはEqとShowのインスタンスである型で且つ以下の6つメソッドを用いて処理可能な数値を値としてもつ型の集合
    • メソッド
      • (+) :: a -> a -> a
      • (-) :: a -> a -> a
      • (*) :: a -> a -> a
      • abs :: a -> a
      • negate :: a -> a
      • signum :: a -> a
    • 除算のメソッドは提供していない。除算は特別なクラス(Integral, Rractional)で処理される
  • Integral 整数クラス
    • IntegralはNumのインスタンスの型であり、値が整数であり、いかに示す整数の商と余りを計算するメソッドを提供する型の集合
    • メソッド
      • div :: a -> a -> a
      • mod :: a -> a -> a
      • これらのメソッドはバッククオートで囲まれ中置で使われることが多い
    • IntとIntegerはIntegralクラスのインスタンス
  • Fractional 分数クラス
    • Numクラスのインスタンスの型であり、値が整数でなく、以下の分数の除算と逆数を計算するメソッドを提供する方の集合
    • メソッド
      • (/) :: a -> a -> a
      • recip :: a -> a
      • 例:
Prelude> 7.0 / 2.0
3.5
Prelude> recip 2.0
0.5
3章 練習問題

1. 以下の値の型は何か?

['a', 'b', 'c'] -- [Char]
('a', 'b', 'c') -- (Char,Char,Char)
[(False, 'o'), (True, '1')] -- [(Bool, Char)]
([False, True], ['0', '1']) -- ([Bool],[Char])
[tail, init, reverse] -- [関数型]、どうやって表現すればいんだろ?

実際にghciで試してみた

Prelude> :type ['a', 'b', 'c']
['a', 'b', 'c'] :: [Char]
Prelude> :type ('a', 'b', 'c')
('a', 'b', 'c') :: (Char, Char, Char)
Prelude> :type [(False, 'o'), (True, '1')]
[(False, 'o'), (True, '1')] :: [(Bool, Char)]
Prelude> :type ([False, True], ['0', '1'])
([False, True], ['0', '1']) :: ([Bool], [Char])
Prelude> :type [tail, init, reverse]
[tail, init, reverse] :: [[a] -> [a]]
Prelude> 

なるほど、[tail, init, reverse] :: [[a] -> [a]]でいいんだね。

2. 以下の関数の方は何か?

second :: [a] -> a
second xs = head(tail xs)

swap :: (a, b) -> (b, a)
swap (x,y) = (y,x)

pair :: a -> b -> (a, b)
pair x y = (x, y)

double :: Num => a -> a
double x = x * 2

palindrome :: [a] -> Bool
palindrome xs = reverse xs == xs

twice :: (a -> a) -> a -> a
twice f x = f(f x)

実際に試してみた

Prelude> :load programing_in_haskell.hs 
[1 of 1] Compiling Main             ( programing_in_haskell.hs, interpreted )
Ok, modules loaded: Main.
*Main> :type second 
second :: [a] -> a
*Main> :type swap 
swap :: (t, t1) -> (t1, t)
*Main> :type pa
pair        palindrome
*Main> :type pair 
pair :: t -> t1 -> (t, t1)
*Main> :type double 
double :: (Num a) => a -> a
*Main> :type palindrome 
palindrome :: (Eq a) => [a] -> Bool
*Main> :type twice 
twice :: (t -> t) -> t -> t
*Main> 

なるほど、doubleの型はこう書くのか。palindromeはEqのインスタンスに対してなのね。あとは正解だった。

4. 一般的に関数の型をEqクラスのインスタンスにするのが実現不可能な理由は何か?どういった場合に実現可能か?ヒント:同じ型の関数が同等であるのは、同等な引数に関して同等な結果を返すときである。

問題の意味がぱっとわからない。噛み砕いて考えてみる。
"関数の型をEqクラスのインスタンスにする"っていうのはその関数が(==)と(/=)メソッドを持つってことだよね。。
違うか、関数型(という集合の)要素である関数fとgが(==)と(/=)を適用できるってことか。
ほんで、f == g っていうのはヒントによるとf x == g xってこと?
うーん、よくわからない。
例えば
hoge :: (Num a) => a -> a
hoge = x * 2
fuga :: (Num a) => a -> a
fuga = x * 2
ってしたら、hoge == fugaになりそうだけど。

なんか疑問点だけ書くつもりが、だらだらになっちゃった。