『ふつうのHaskellプログラミング』

夕べ帰ったらAmazon.co.jpから届いていた。

[amazonjs asin=”4797373970″ locale=”JP” title=”ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門”]

だけど,今週はちょっと読む時間がない。
入門Haskell―はじめて学ぶ関数型言語』 もまだ消化しきれてないし。
まぁ,焦っても仕方がない。ゆっくり行こう。

高階関数版 filter’

下のエントリは前段でこっちが本題。id:hyukiさんとこにコメントをつけたんだけど,忘れないようにこっちにも書いておく。

filter' f xs = foldr (\m n -> if f m then m:n else n) [] xs

結果。

*Main> take 50 $ filter' odd [1..]
[1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,
57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99]

major

リスト中に,関数 f(述語って言うの?)を満たす要素のほうが多ければ True をかえす,というのを考えた。all と any の中間みたいなもの。

major f xs = (length $ filter f xs) > (length $ filter (not . f) xs)

結果。

*Main> major odd [1..5]
True
*Main> major even [1..5]
False

地道に数を数えるならこうかな。

major f xs = fst count > snd count
  where count = foldl (\(ct, cf) x -> if f x then (ct+1, cf) else (ct, cf+1)) (0,0) xs
*Main> major odd [1..5]
True
*Main> major even [1..5]
False

はてなグループとコメントについて

id:takatoh:20060604:group についた hyuki さんのコメント。

参加ありがとうございます。ところで、コメントつけるときに「グループに参加」というのはどういう状況でしたか?いちおう誰で も書き込めるようになっていたはずですが…。

たぶん,はてなにログインした状態でコメントしようとしたので,グループに参加するよう促されたのではないかと思います。回避する方法があったのかもしれませんが,別段,断る理由もなかったのでそのまま参加しました。

ところが。

つけたコメントの,名前の部分がリンクになってるんですが,そのリンク先がここじゃなくて Haskellグループ内の日記(使ってないのでカラ)になってしまってます。これはどうしたらいいんだろう。時間があいたら調べてみます。

レコード

レコードは代数的でないデータ型。C の構造体のようなもの。うまい例を思いつかないから,本に載ってるのをそのまま書くとこんな感じ。

data Point = Pt {x :: Integer, y :: Integer}

x,y をフィールドといい,それぞれ Integer 型の値を取る。具体的に値を定義するにはこうする。

Pt { x = 1, y = 4 }

代数的データ型を使っても同じようなことはできる。データ構成子が複数の引数を取ることにすればいいし,タプルにしてもいい。これらとレコードが違うのは2点。

  • 名前がついているのでわかりやすい。
  • フィールド名が,同時にフィールドにアクセスするための関数になっている。

2つ目のほうはこんな感じだ。

*Main> x Pt {x = 2, y = 3}
2

x という関数が定義されてるってことだな。

*Main> :t x
x :: Point -> Integer

さて,じゃあおなじフィールド名を持つレコードを宣言するとどうなるか。

data TP = Tp { x :: Int, y :: Int } deriving (Eq, Show)

data Point = Pt { x :: Integer, y :: Integer } deriving (Eq, Show)

これを GHCi にロードすると

Prelude> :l record.hs
Compiling Main             ( record.hs, interpreted )

record.hs:3:18:
    Multiple declarations of `Main.x'
    Declared at: record.hs:1:15
                 record.hs:3:18

record.hs:3:32:
    Multiple declarations of `Main.y'
    Declared at: record.hs:1:25
                 record.hs:3:32
Failed, modules loaded: none.

あー,やっぱりだめなわけね。

型シノニムと新しい型

型シノニム(type synonym)とは,既存のデータ型に別名をつけるというもの。String が [Char] であるのがよい例。
型シノニムを宣言するには type 宣言を使う。

type String = [Char]

newtype 宣言は「新しい型」を宣言する。type 宣言と data 宣言の中間的なもので,実体は既存の型とおなじであっても,元の型とは違う扱いをしたい,という場合に使うらしい。たとえば「実体は整数だが負の値は取らない」みたいな。
でもこれ,具体的な宣言の仕方が書いてない。まあ,あまり使わないのかな。

パスカルの三角形(みたび)

cf. id:rubyco:20060531:pascal
cf. id:epics:20060530:1149000099

Ruby の Enumerable#inject は Haskell では foldl だ。
というわけで早速やってみよう。ただし,無限リストを扱えるように foldr をつかう。早くしないと 結城さんに先を越されちゃうぞ。

pascalTriangle = foldr (\n i -> [pascal n] ++ i) [[]] [1..]
  where pascal x | x == 1 = [1]
                 | otherwise = (\xs -> zipWith (+) (0:xs) (xs ++ [0])) (pascal (x-1))

結果。

*Main> putStr $ unlines $ map show $ take 10 $ pascalTriangle
[1]
[1,1]
[1,2,1]
[1,3,3,1]
[1,4,6,4,1]
[1,5,10,10,5,1]
[1,6,15,20,15,6,1]
[1,7,21,35,35,21,7,1]
[1,8,28,56,70,56,28,8,1]
[1,9,36,84,126,126,84,36,9,1]

あー,でも,n 列目を作るのに再帰してるのが無駄っぽいなぁ。

data宣言に型変数をつかう

データ構成子の引数は具体的な型じゃなくて型変数でもかまわない。

data Cream a b c = Eric a | Jack b | Ginger c deriving (Show)
*Main> [Eric 10, Ginger 'g', Jack 2.4]
[Eric 10,Ginger 'g',Jack 2.4]
*Main> [Eric 1.2, Ginger 3, Jack "2.4"]
[Eric 1.2,Ginger 3,Jack "2.4"]
*Main> [Eric 1.2, Ginger 3.1, Jack 2.4]
[Eric 1.2,Ginger 3.1,Jack 2.4]

引数にどんな型がきてもOK。
ただし,型変数は = の左側にも書いておなかなければいけない。↓の例では c が無いのでエラーになる。

data Cream a b = Eric a | Jack b | Ginger c deriving (Show)
*Main> :l datatype.hs
Compiling Main             ( datatype.hs, interpreted )

datatype.hs:1:42: Not in scope: type variable `c'
Failed, modules loaded: none.

deriving修飾子

新しく宣言した型を何らかの型クラスのインスタンスとして宣言するには,instance 宣言を使う。が,いくつかの型クラスに関してはもっと簡単な方法がある。data 宣言と一緒に deriving 修飾子を使うのがそれ。
次のようにすると,Cream 型を宣言すると同時に,Eq,Show,Read クラスのインスタンスとして宣言できる。

data Cream = Eric | Jack | Ginger deriving (Eq, Show, Read)
*Main> Eric == Eric
True
*Main> Eric /= Jack
True
*Main> show Ginger
"Ginger"

deriving で指定できるのは,Eq,Ord,Enum,Bounded,Show,Read の6つだけ。この6つは頻繁に使われるからこういう簡便な方法が用意されているってことのようだ。とくに ghci で表示させるには Show クラスのインスタンスである必要があるので,黙って指定しておくべきだろう。

で,これらのクラスにどんなメソッドがあるのか,調べてみた。

  • Eq クラス
    • (==) :: a -> a -> Bool
    • (/=) :: a -> a -> Bool
  • Ord クラス
    • compare :: a -> a -> Ordering
    • (<) :: a -> a -> Bool
    • (<=) :: a -> a -> Bool
    • (>) :: a -> a -> Bool
    • (>=) :: a -> a -> Bool
    • max :: a -> a -> a
    • min :: a -> a -> a
  • Enum クラス
    • succ :: a -> a
    • pred :: a -> a
    • toEnum :: Int -> a
    • fromEnum :: a -> Int
    • enumFrom :: a -> [a]
    • enumFromThen :: a -> a -> [a]
    • enumFromTo :: a -> a -> [a]
    • enumFromThenTo :: a -> a -> a -> [a]
  • Bounded クラス
    • minBound :: a
    • maxBound :: a
  • Show クラス
    • showsPrec :: Int -> a -> Shows
    • show :: a -> String
    • showList :: [a] -> Shows
  • Read クラス
    • readsPrec :: Int -> ReadS a
    • readList :: ReadS [a]

いっぱいあるな,とくに Ord クラスと Enum クラスが。
ところで上のリストは Hoogle で調べたんだけど,Read クラスのメソッドに read がない。と思ったら別のところに書いてあった。これってつまり,read は Read クラスのメソッドじゃないってことか?