2006-06-11
■ let式とwhere節 
ふつけるp.187まで読みました。
- let式は式なので値を持つ。
- where節は節なので値は持たない。
- let式は変数の定義用に使うことが多い。
- where節は関数の定義用に使うことが多い。
なるほど。where節はよく使うけれど、letってあまり使ったことないなあ。
■ アズパターン 
あずまんがアズパターンの練習です。
tails' :: [a] -> [[a]] tails' [] = [[]] tails' as@(x:xs) = as : tails' xs
実行結果です。
*Main> tails' "hello" ["hello","ello","llo","lo","o",""]
アズパターンは「変数1@変数2@パターン」のように複数個おけます。
以下、無意味な練習。
dtails :: [a] -> [[a]] dtails [] = [[],[]] dtails bs@as@(x:xs) = as : bs : dtails xs
実行結果です。
*Main> dtails "hello" ["hello","hello","ello","ello","llo","llo","lo","lo","o","o","",""]
■ データコンストラクタによるパターン 
ふつけるp.173まで読みました。
[]は空リストを作るパターンで、:はリストを作成するパターンです。
head' :: [a] -> a head' [] = error "empty list" head' (x:xs) = x
(x:xs)は((:) x xs)のように書いてもよいはずだと思いました。
head'' :: [a] -> a head'' [] = error "empty list" head'' ((:) x xs) = x
実行結果です。
*Main> head'' "" *** Exception: empty list *Main> head'' "Hello" 'H'
よしよし。
■ _パターン 
これはエラー。
foo x x = "Hello"
こんなエラー。
a.hs:1:
Conflicting definitions for `x'
In the definition of `foo'
これならエラーにならない。_パターンは変数を束縛しないから。
foo _ _ = "Hello"
■ GHCの-Wオプション 
ふつけるp.168まで読みました。
GHCで-Wオプションをつけると、パターンマッチで実行時エラーになるかどうか検出できます。
module Main where main = do print (foo 0) foo x | x > 0 = 1 foo x | x < 0 = negate 1
> ghc -W a.hs -o a
a.hs:5:
Warning: Pattern match(es) are non-exhaustive
In the definition of `foo': Patterns not matched: _
とやって気がついたのだけれど、ガードがある場合は無条件で警告でるのかもね。
試してみよう。
module Main where main = do print (foo 0) foo x | True = x
うーん、↑だと警告は出ない。
module Main where main = do print (foo 0) foo x | x > 0 || x <= 0 = x
うーん、↑だと警告が出る。
■ オフサイドルール 
ふつけるp.165まで読みました。
- オフサイドルール
- オフサイドライン: let, where, do, ofの次の単語の場所
- 式の継続
a = x + y + z
where
x = 1
y = 2 + 3 + 4 + 5 + 6
+ 7 + 8 + 9
z = 10
■ 関数の無限リスト 
nobsunのrmapのエントリを読んでいて、(いまさらながら)はっと気づいた。
map (+) [1..]
これって、部分適用になるから、[(1+),(2+),(3+),(4+),... ]っていう「関数の無限リスト」になるんだ…。
関数を普通のデータのように扱うっていう感覚、まだまだ身についていないなあと実感した次第。
■ Haskellとメモ化 
ひげぽんさんの「Gaucheのforceはメモする」というのを読んで「そういえば、Haskellの場合には明示的なメモ化の必要はないんじゃないか?」と思いました。
なぜそう思ったかというと、そもそもHaskellには代入がないので、xの値はいつも同じだからです(参照透過性)。つまり「いつもメモ化されている」と思っていてよいのでは?
…とここまで考えてからグーグルで「Haskell memoise」を検索。すると(予想通り)sampou.orgが見つかる。ところが「WWW.SAMPOU.ORG server clashed」というメッセージが。大丈夫でしょうか?
グーグルのキャッシュにリンク。
うーむ。メモ化が不要なわけじゃないのか…まだよくわからず。眠いのでまた今度。
■ 一つのデータに関数リストをapply 
map f xsによって、xsの各要素xに対してf xをしたリストが得られるなら、それの逆(というか双対?)のようなこともできますよね。
つまり、fsの各要素fに対して決まったxを与えてリストにするようなの。仮にrmapとしてみます。
rmap :: a -> [a -> b] -> [b] rmap x [] = [] rmap x (f:fs) = f x : rmap x fs a = (1 +) b = (2 *) c = \x -> x * x d = \x -> x * x + 1 z = rmap 10 [a,b,c,d] -- [11,20,100,101]になる
高階に考えると、mapを使えばよいはず。
rmap :: a -> [a -> b] -> [b] rmap x fs = map (\f->f x) fs
eta reductionを行う。
rmap :: a -> [a -> b] -> [b] rmap x = map (\f->f x)
追記:nobsunからコメントをもらった。
うーん、すごいな。
rmap :: a -> [a -> b] -> [b] rmap = map . flip ($)
はじめ、さっぱりわかりませんでした。小一時間考えました。得た知識を以下に列挙。
- そうか、($)も関数なんだ。
- map . flip ($)は、(map.flip) ($)ではなく、map . (flip ($)) だ。(ここを誤解していた。GHCi の :i でチェックしてわかった)
- map . flip ($)は flip.mapではない!(ここも誤解していた。mapの入力をひっくりかえすのではなく、mapが行う個々のapplyをひっくり返すのだ)
- flip ($) は、関数->入力を入力->関数にする。だからmapと合成すれば、「一つのデータと関数リスト」が扱えるのか(直感的理解)
ここでふと考えた。($)の型の、
っていうのは、
って考えても良いよね。ということは、
と同じではあるまいか。ということは、($)の代わりにidでも動くのではないか。つまり、こういうこと。
rmap :: a -> [a -> b] -> [b] rmap = map . flip id
試してみよう。
a = (1 +) b = (2 *) c = \x -> x * x d = \x -> x * x + 1 z = rmap 10 [a,b,c,d]
実行結果です。
*Main> z [11,20,100,101]
やたっ! ……でも、正しいのだろうか。
■ コメント 
ふつけるp.162まで読んだ。
- 一行コメント --
- コメント {- -} ネスト可能
- literate style, 拡張子 .lhs
- HaddockはHaskellのjavadoc
- http://www.haskell.org/haddock/
rmap x = map (\f -> f x)
-- 1. fを消す。id と flip を使う
rmap x = map (\f -> id f x)
rmap x = map (\f -> flip id x f)
rmap x = map (flip id x)
-- 2. xを消す。. (関数合成)を使う
rmap x = map ((flip id) x)
rmap x = (map . (flip id)) x
rmap = (map . (flip id))
rmap = map . flip id