パスカルの三角形

id:hyuki さんのを見て。

 cf. http://sicp.g.hatena.ne.jp/hyuki/20060512/pascal

といっても Scheme はよくわからんので Ruby版(id:rubyco:20060429:pascal)を見ながら書いた。

combination n k | k == 1 = 1
                | k == n = 1
                | otherwise = (combination (n-1) k) + (combination (n-1) (k-1))

line n = map (combination n) [1..n]

pascalTriangle = do mapM putStrLn (map (show . line) [1..10])

実行。

*Main> 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]

練習問題

入門Haskell―はじめて学ぶ関数型言語」 p.73 より。
①は面倒なだけなのでパス。

②foldr の定義を書きなさい。

foldr は後ろから引数の関数を適用するんだからこうだろう

myFoldr f a x:[] = f x a
myFoldr f a (x:xs) = f x (myFoldr f a xs)

実行。

*Main> myFoldr (++) "d" ["a","b","c"]
"abcd"
*Main> foldr (++) "d" ["a","b","c"]
"abcd"

OK。ちゃんとできてるな。

③reverse は,リストを逆転させる関数です。たとえば reverse [1,2,3] は [3,2,1] になります。この reverse を定義しなさい。

myReverse [] = []
myReverse (x:xs) = myReverse xs ++ [x]
*Main> myReverse "abcde"
"edcba"

こちらもOK。

関数の合成

2つの関数を f と g とすれば f . g と単純に行くのは g の引数が1つの場合だけ。2つ以上の引数をとる場合にはちょっと複雑になる。次のような関数で確かめてみる。

f a = a:[]

g a = a:[]
g2 a b = a:b:[]
g3 a b c = a:b:c:[]

まずは簡単な f と g

*Main> :t f . g
f . g :: a -> [[a]]

引数を2つとる g2 に対して同じようにやると

*Main> :t (f . g2)
(f . g2) :: a -> [a -> [a]]

こんな型になって2つの引数をうまく追い出せない。

*Main> (f . g2) 1 2

<interactive>:1:0:
    Couldn't match `[a -> [a]]' against `t -> t1'
      Expected type: [a -> [a]]
      Inferred type: t -> t1
    Probable cause: `(f . g2)' is applied to too many arguments in the call
        ((f . g2) 1 2)
    In the definition of `it': it = (f . g2) 1 2

追い出してやるには,関数合成を2段階にする。

*Main> :t ((f .) . g2)
((f .) . g2) :: a -> a -> [[a]]
*Main> ((f .) . g2) 1 2
[[1,2]]

引数が3つの場合には3段階。

*Main> :t (((f .) .) . g3)
(((f .) .) . g3) :: a -> a -> a -> [[a]]
*Main> (((f .) .) . g3) 1 2 3
[[1,2,3]]

逆に1つ目の関数のほうが複数引数の時は単純に合成できる。

*Main> :t g3 . f
g3 . f :: a -> [a] -> [a] -> [[a]]

ただし,引数のほうがややこしい。上の例で行くと1つ目の引数は f の引数,2つ目以降が g3 の2番目3番目の引数となる。こうすればわかりやすい。

g3 (f a) b c

実行。

*Main> (g3 . f) 1 [2] [3]
[[1],[2],[3]]

じゃあ,引数が3つと2つならどうだ。

*Main> :t (g3 .) . g2
(g3 .) . g2 :: a -> a -> [a] -> [a] -> [[a]]
*Main> ((g3 .) . g2) 1 2 [3] [4]
[[1,2],[3],[4]]

ああ,ややこしい。

練習問題

今日は目先を変えて練習問題をやろう。
入門Haskell―はじめて学ぶ関数型言語」 p.72 より。

①前ページの実装から,takeとdropに大きな値や負の値が入った場合の対処をしなさい。

前ページの実装とはこれ。Prelude の関数とかぶってはいけないので名前は変えてある。

mytake 0 _ = []
mytake n (x:xs) = x : mytake (n-1) xs

mydrop 0 xs = xs
mydrop n (x:xs) = mydrop (n-1) xs

mytake に大きな値を入れるとリストそのもの,負の値を入れると空リストが返るようにする。逆に mydrop に大きな値を入れると空リスト,負の値を入れるとリストそのものが返るようにする。

mytake _ [] = []
mytake 0 _ = []
mytake n (x:xs) | n < 0 = []
               | otherwise = x : mytake (n-1) xs

mydrop _ [] = []
mydrop 0 xs = xs
mydrop n (x:xs) | n < 0 = x:xs
                | otherwise = mydrop (n-1) xs 

もっとすっきり書けそうだけどまぁいいか。実行結果。

*Main> mytake 10 [0,1,2,3,4,5]
[0,1,2,3,4,5]
*Main> mytake (-2) [0,1,2,3,4,5]
[]
*Main> mydrop 10 [0,1,2,3,4,5]
[]
*Main> mydrop (-2) [0,1,2,3,4,5]
[0,1,2,3,4,5]

ふたつめ。

②take と drop を同時に実行する splitAt :: Int -> [a] -> ([a], [a]) があります。たとえば
splitAt 2 [1,2,3,4] — ([1,2], [3,4])
のように,結果のタプルの第1要素が take ,第2要素が drop になります。この splitAt を定義しなさい。また,take と drop を splitAt を使って定義し直しなさい。

まずは splitAt のほうから(名前は変えてある)。

mysplitAt _ [] = ([], [])
mysplitAt n xs = ((take n xs), (drop n xs))

結果。

*Main> mysplitAt 2 [1,2,3,4]
([1,2],[3,4])

よし。じゃ,これを使って mytake と mydrop を定義し直すと

mytake n xs = fst $ mysplitAt n xs

mydrop n xs = snd $ mysplitAt n xs

結果は

*Main> mytake 3 [1,2,3,4,5]
[1,2,3]
*Main> mydrop 3 [1,2,3,4,5]
[4,5]

もうひとつ。

③takeWhile は,(a -> Bool) -> [a] -> [a] という型です。takeと似ていますが,決まった数だけとるのではなく,要素を計算し たら真である限りtakeし,一度でも失敗したら残りは返しません。これを定義しなさい。

こんなんでどうかな。

mytakeWhile _ [] = []
mytakeWhile f (x:xs) | f x = x : mytakeWhile f xs
                     | otherwise = []
*Main> mytakeWhile (\x -> x > 0) [3,2,1,0,-1,-2,-3]
[3,2,1]
*Main> mytakeWhile (\x -> x < 0) [3,2,1,0,-1,-2,-3]
[]

ポイントフリースタイル

今日の一行 – ポイントフリースタイルを参考にして mytake から引数を消してみる。

mytake n xs = fst $ mysplitAt n xs
            ↓
mytake n xs = fst (mysplitAt n xs)
            ↓ 関数合成を使って xs を外に追い出す
mytake n xs = (fst . (mysplitAt n)) xs
            ↓ xs を消す
mytake n    = (fst . (mysplitAt n))
            ↓ 関数合成演算子を前置
mytake n    = (.) fst (mysplitAt n)
            ↓ もう一度,関数合成演算子を使って今度は n を追い出す
mytake n    = (((.) fst) . mysplitAt) n
            ↓ n を消す
mytake      = ((.) fst) . mysplitAt

結果。

*Main> mytake 2 [1,2,3,4]
[1,2]

おお!うまくいった!すごいな。
ポイントは関数合成とその演算子を前置するところだな。

んー,待てよ。単純にこうやったらどうだ?

mytake = fst . mysplitAt
*Main> :l mysplitAt.hs
Compiling Main             ( mysplitAt.hs, interpreted )

mysplitAt.hs:25:20:
    Couldn't match `(a, b)' against `t -> t1'
      Expected type: (a, b)
      Inferred type: t -> t1
      Expected type: Int -> (a, b)
      Inferred type: Int -> [a1] -> ([a1], [a1])
    In the second argument of `(.)', namely `mysplitAt'
Failed, modules loaded: none.

だめか。
そうか, (.) fst が関数を引数にとる関数で,これと mysplitAt を合成すると,mysplitAt の引数が外に追い出されるんだ。つまりこう。

mytake n xs = (((.) fst) . mysplitAt) n xs

で,引数が消える,と。

スーパークラス/サブクラス

今日もメモ程度。

値の大小関係を表すには Ord クラス。「入門Haskell」によればOrd クラスの定義は次のようである,らしい。

class Eq a => Ord a where
  (>) :: a -> a -> Bool
  (<) :: a -> a -> Bool
  (>=) :: a -> a -> Bool
  (<=) :: a -> a -> Bool
  compare :: a -> a -> Ordering
  max :: a -> a -> a
  min :: a -> a -> a

大小関係を判定するには等しいか否かを判定できなければならない,というわけで Eq a => Ord a となっている。このとき,Eq は Ord のスーパークラスであるといい,逆に Ord は Eq のサブクラスであるという。
関数の型のときにでてきた context みたいなものか。

ところで,Ord クラスのインスタンスに Bool も含まれてるんだけど,これってどんなときに使うんだろう。

Prelude> True < False
False
Prelude> True > False
True

True は False より大きいとか言われても,何それ。

型クラス

昨日(id:takatoh:20060506:type)の最後にあげた関数の型

Prelude> :type map (\x -> x * 2)
map (\x -> x * 2) :: (Num a) => [a] -> [a]

の中に現れる (Num a) => という部分は,a が Num クラスのインスタンスでなければならないことを表している。つまりこの場合には,引数に 2 を掛けるという関数なのだから,引数は 2 と掛け算ができる型でなければならない。それが Num クラスのインスタンス,というわけだ。
この,型変数 a に対するいわば制約をコンテキスト(context)という。

型クラスというのは,いくつかの型に共通する性質をまとめたもの,というふうに理解したらいいだろうか。
たとえば,「一致するか否かを比較判定できる」という性質は Eq クラスにまとめられているけど,この性質は 文字(Char)や数値(Int,Float)などに共通する。このとき,Char や Int を Eq クラスのインスタンスという。というか,Char や Int は Eq クラスのインスタンスとして定義されている。

クラスに定義されている関数をメソッドといい,Eq クラスには比較のためのメソッド (==) と (/=) が定義されている。

Prelude> :type (==)
(==) :: (Eq a) => a -> a -> Bool
Prelude> :type (/=)
(/=) :: (Eq a) => a -> a -> Bool

Eq クラスのインスタンスである型ならこのメソッドを適用できる。

Prelude> 'a' == 'a'
True
Prelude> 100 == 200
False
Prelude> 1.24 /= 1.25
True

リストでも大丈夫みたいだ。

Prelude> [1,2,3] == [1,2,3]
True

代表的なクラスをいくつか。

Eq Num Show Read Ord

ところで,ふだん Ruby をメインに使っている俺としては,このクラスとインスタンスの関係にはちょっと違和感がある。
Haskell でいう Char とか Int とかの型(type)が Ruby ではクラスであって,その具体的な値(”abc” とか 10 とか)をインスタンスという。Haskell のクラスに当たるものは Ruby には……ないよな。あえて言えばスーパークラスか?……それも違うな。
逆に Ruby のインスタンスに当たるものは Haskell ではなんと言うんだろう。

関数の型

連休前は仕事がつまってこのblogの更新もままならないほどのGW進行だったというのに,いざ休みになってみると一転,だらけモードで逆の意味でGW進行になってしまった。なんてこった。
気を取り直していこう。まずはこの間(id:takatoh:20060429:tr)はまった関数の型から。

Haskell の関数には型がある。簡単に言うとどんな型の引数をとってどんな型の値を返すか,というのが関数の型。
GHCi では :type コマンドで確認できる。たとえば length は何かのリストを引数にとってその長さ(Int型)を返す。

Prelude> :type length
length :: [a] -> Int

この表記を型式(かたしき)という。
[a] は「何かのリスト」を表していて,その「何か」はChar でも Int でもそのほかの何でもいい。とにかくリストならいいわけだ。
これを多相型(polymorphic type)といい,表記に使われている a を型変数(type variable)という。

Prelude> :type zip
zip :: [a] -> [b] -> [(a, b)]

型変数に a と b が使われているのは,一般に違う型であることを表している。おなじ型でもかまわない。
関数を引数にとる高階関数はこんな風に表される。

Prelude> :type map
map :: (a -> b) -> [a] -> [b]

(a -> b) が引数の関数を表している。

さて,ここからがこの間のキモに関わる部分。型式のなかに -> という記号が使われているが,これは

  • 型に関する二項演算子で
  • 右結合

だ。つまり,map は

map :: (a -> b) -> ([a] -> [b])

とも解釈できる。この意味するところは,「(a -> b) である関数を一つ引数にとって,([a] -> [b])である関数を返す」関数だということ。実際にやってみると

Prelude> :type map (\x -> x * 2)
map (\x -> x * 2) :: (Num a) => [a] -> [a]

引数にした関数の都合で両方とも a になってしまったが,ちゃんと「リスト([a])を引数にとってリスト([a])を返す」関数になっている。

それと, (Num a) => の部分も引数にした関数の都合で現れたものだけど,これは別のエントリで改めて書くことにしよう。
今日はこれくらいで。

詳解・id:nobsunさん版 tr

ちょっと大げさか。

 cf. OSS WEB|ossz|oneline|2006-04-28

tr :: [Char] -> [Char] -> (String -> String)
tr set1 set2 = map trans
  where
    trans = foldl (flip $ uncurry add) id (zip set1 set2)
    add k v s q | k == q = v
                | otherwise = s q

これがちゃんと動くことは試してみればすぐにわかる。けど,どうやって動くのかがわかんないぞ。
というわけで,細かく見ていくことにしよう。想定する読者は1週間後の自分。

まずは tr の引数から。定義には書かれていないけど3つの引数をとる。= の右側の map がとるはずの2つの引数のうちひとつしか書いてないから,もうひとつの引数が隠れているのがわかる。これを str としよう。
それからもうひとつ,trans のなかで set1,set2 が使われてるから,この2つは trans の引数だと考えることもできる。さらに str のうちの1文字を引数に取るはずだから trans は3つの引数をとる関数ということになる。
これらを明示的に書いてやるとこうなる。

tr set1 set2 str = map (trans set1 set2) str
  where
    trans set1 set2 c = foldl (fllip $ uncurry add) id (zip set1 set2) c
    add k v s q | k == q = v
                | otherwise = s q

つぎは add。これが実際の文字の変換を行う関数だろう。引数を4つとって,うち3つめは何らかの関数。
で,問題は trans の中身。ここがわからない。foldl は引数を3つとる関数だけど,ここでは4つとっているように見える。なぜだ,おかしい。

わかんないから,それぞれの関数の型を順に追ってみることにしよう。ghci で関数の型を確認できるように wehre 節をなくして次のように書き換える。

tr set1 set2 = map (trans set1 set2)
trans set1 set c = foldl (flip $ uncurry add) id (zip set1 set2) c
add k v s q | k == q = v
            | otherwise = s q

それじゃいってみよう。まずは trans。

trans :: (Eq b) => [b] -> [b] -> b -> b

思った通り3引数。
つぎはその中身のほうを少しずつ確かめてみよう。foldl の第1引数と思われる filp $ uncurry add の中を少しずつ。
add

add :: (Eq a) => a -> t -> (a -> t) -> a -> t

uncurry add

uncurry add :: (Eq a) => (a, b) -> (a -> b) -> a -> b

add の第1,第2引数をひとつのタプルにまとめている。t が b に変わってるけどこれは型変数の名前が変わっただけだから気にしない。
つぎ,flip $ uncurry add

flip $ uncurry add :: (Eq a) => (a -> b) -> (a, b) -> a -> b

第1引数と第2引数を入れ替えてるだけだな。
つぎは foldl (flip $ uncurry add) ……のまえに foldl を確認。

foldl :: (a -> b -> a) -> a -> [b] -> a

ほーらおかしい。第1引数の関数は (a -> b -> a) じゃなきゃいけないのに (flip $ uncurry add) は(a -> b) -> (a, b) -> a -> b だ。なのに実際に確かめてみると

foldl (flip $ uncurry add) :: (Eq a) => (a -> b) -> [(a, b)] -> a -> b

なぜだ。(flip $ uncurry add) の型 (a -> b) -> (a, b) -> a -> b とfoldlの第1引数の型 (a -> b -> a) を眺めながら考える。

……考える。

……まだ眺める。

……一晩寝る。

あー!!そうか,カリー化だ。3引数の関数に引数を1つ与えると2引数の関数が得られる。もう1つ引数を与えれば1引数の関数を得る。
つまり flip $ uncurry add を3引数の関数じゃなく,2引数をとって1引数の関数を返す関数だと考えればいい。型式で書くとこう。

flip $ uncurry add :: (Eq a) => (a -> b) -> (a, b) -> (a -> b)

これなら foldl の第1引数の型と対応するじゃないか。

(a -> b) -> (a, b) -> (a -> b)
   |          |          |
   |          |          |  (a -> b) と a ,(a, b) と b が対応する
   |          |          |
   a     ->   b    ->    a

この対応にしたがって foldl の型を書き換えてみると

foldl :: ((a -> b) -> (a, b) -> (a -> b)) -> (a -> b) -> [(a, b)] -> (a -> b)

これに (a -> b) -> (a, b) -> (a -> b) である (flip $ uncurry add) を与えて得られる関数の型は

(a -> b) -> [(a, b)] -> (a -> b)

上で書いた foldl (flip $ uncurry add) の型と一致する(一番右の a -> b を括弧でくくってやればいい)。つまり,この関数は「 (a -> b) である関数と [(a,b)] --(a,b)であるタプルのリスト--を引数にとって (a -> b) である関数」を返すってことだ。

ここまでくればあとは簡単だ。
実際の引数を見てみると,第1引数には id,第2引数には zip で得られるタプルのリストとなっている。id の型は (a -> a) だけど,これは a と b がおなじ型だと考えればいい。set1 と set2 は両方とも文字列で,こっちも a と b がおなじになるから問題はない。でもってその結果は(bをaに置き換えて書けば) (a -> a) である関数,となる。
結局,foldl (flip $ uncurry add) id (zip set1 set2) は「文字を引数にとって,適切に変換した文字を返す関数」ってことになる。どう変換するかは関数自体が知っている--(zip set1 set2)がいわば変換テーブルになっている。これが trans の実体って訳だ。

tr 全体ではさらに簡単。trans を str の頭から順に適用しているだけだ。

ふー。やっと解読したって感じだな。なんか,関数は値を返すものだというのがしみこんでいて,「関数を返す関数」というのに頭が回らない。なんだか「関数を返す関数」を自在に使えるようになることが関数言語使いになるってことなのかと思った。

ところで,話は変わるけど。

あれっ.これだめだ.tr “aaa” “xyz” “abracadabra” は tr “a” “z” “abracadabra” と同じじゃなきゃいけないのに...

これはそういうものなんだろうか。俺のPCに入ってる tr コマンドでは ‘a’ は ‘x’ に変換される。

>echo abracadabra | tr aaa xyz
xbrxkxdxbrx

このコマンド,Windows には付いてないからどこからか手に入れたもののはず。よく覚えてないけど GNU だか Cygwin だったかじゃなかったかなぁ。

tr

tr コマンドもどき。文字列中の文字を置き換える。

import Data.List

tr _ _ [] = []
tr str1 str2 (c:cs) = (tr' str1 str2 c):(tr str1 str2 cs)
  where tr' str1 str2 c = case (elemIndex c str1) of
                            Just n  -> str2 !! n
                            Nothing -> c

キモは elemIndex の返り値。elemIndex は文字列 str1 中の文字 c のインデックス(0から始まる)を返す関数だけど,返り値の型が Int じゃなくて Maybe Int なので,case で場合分けをしている。はじめこの返り値の型に気づかなくて悩んでしまった。こういうのは真っ先に確認すべきだな。

*Main> :type elemIndex
elemIndex :: (Eq a) => a -> [a] -> Maybe Int

結果。

*Main> tr "ab" "AB" "banana"
"BAnAnA"

ところで,引数に文字列を3つとるのはいいけどその名前が str1,str2 っていうのは我ながらどうかと思う。こういうのってセンスが問われるよなぁ。

追記:
再帰じゃなくて map を使うようにした。ついでに引数の名前を短く。

import Data.List

tr _ _ [] = []
tr s1 s2 str = map (tr' s1 s2) str
  where tr' s1 s2 c = case (elemIndex c s1) of
                        Just n  -> s2 !! n
                        Nothing -> c

さらに追記:
tr’ の引数を省略。ちょっとすっきりした。こうやって where 節で局所定義された関数から外側の関数の引数を直接参照できるのっておもしろいねぇ。

import Data.List

tr _ _ [] = []
tr s1 s2 str = map tr' str
  where tr' c = case (elemIndex c s1) of
                  Just n  -> s2 !! n
                  Nothing -> c
*Main> tr "ab" "AB" "banana"
"BAnAnA"