Daniel P. Wright
このスライド自体をコンパイルして実行できる!
Haskellの基本関数を再定義するつもりなんで、とりあえず隠さないと
> import Prelude hiding (sum, foldr, sequence_, replicate, take, repeat, (>>))
基本的に、中学校で代数学を勉強したときの変数置換と近い
\[ \begin{align} x &= 49 \\ y &= x + 1000 \\ \\ y &= 1049 \end{align} \]
\[ \begin{align} 2x + y &= 4 \\ x - y &= -1 \end{align} \]
方程式を組み替えたら同じ方法で解けます
\[ \begin{align} y &= 4 - 2x \\ x &= y - 1 \end{align} \]
\[ \begin{align} x &= y - 1 \\ &= (4 - 2x) - 1 \\ &= 3 - 2x \\ 3x &= 3 \\ x &= 1 \\ \end{align} \] |
\[ \begin{align} y &= 4 - 2x \\ &= 4 - 2(y - 1) \\ &= 4 - 2y + 2 \\ 3y &= 6 \\ y &= 2 \end{align} \] |
「黄金数」と呼ばれる \(\phi = \frac{1 + \sqrt{5}}{2}\)
\(\phi^2 = \phi + 1\) を証明せよ
\[ \begin{align} \phi^2 &= \frac{1 + \sqrt{5}}{2} \times \frac{1 + \sqrt{5}}{2} \\ &= \frac{(1 + \sqrt{5})(1 + \sqrt{5})}{4} \\ &= \frac{1 + \sqrt{5} + \sqrt{5} + \sqrt{5}^2}{4} \\ &= \frac{6 + 2(\sqrt{5})}{4} \\ &= \frac{3 + \sqrt{5}}{2} = \frac{2}{2} + \frac{1 + \sqrt{5}}{2} \\ &= 1 + \phi \end{align} \]
Haskell Reportによると、foldr
はこんな感じ
> foldr f z [] = z
> foldr f z (x:xs) = f x (foldr f z xs)
foldr (+) 0 [1, 2, 3, 4]
≡ 1 + 2 + 3 + 4
を証明してみよ
sum
とfoldr (+) 0
の等しさを証明しよう
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
> sum [] = 0
> sum (x:xs) = x + sum xs
やり方は帰納法証明みたいな感じになる
リストは下記のように考えられる
[1, 2, 3, 4] ≡ 1 : 2 : 3 : 4 : []
[]
は空リスト(:)
(コンズ)はリストの先頭に追加する関数実はfoldr
は、(:)
にf
と[]
にz
を交換するものだ!
foldr f z (1:2:3:4:[]) = 1 `f` 2 `f` 3 `f` 4 `f` z
foldr (+) 0 (1:2:3:4:[]) = 1 + 2 + 3 + 4 + 0
foldr (*) 1 (1:2:3:4:[]) = 1 * 2 * 3 * 4 * 1
foldr (:) [] (1:2:3:4:[]) = 1 : 2 : 3 : 4 : []
-- ∴ foldr (:) [] ≡ id
証明は?
証明は定義による
foldr f z [] = z -- `[]`と`z`の交換
foldr f z (x:xs) = x `f` (foldr f z xs) -- `(:)`と`f`の交換
> main = do
> putStrLn "What is your name?"
> name <- getLine
> replicateM_ 3 $ putStrLn ("Hello " ++ name)
do記法から翻訳すると…
> main' =
> putStrLn "What is your name?" >>
> getLine >>= \name ->
> replicateM_ 3 $ putStrLn ("Hello " ++ name)
出力は本当に3回出るか?
(この例は http://www.haskellforall.com/2013/12/equational-reasoning.html による)
> replicateM_ n x = sequence_ (replicate n x)
sequence_
を調べると> sequence_ ms = foldr (>>) (return ()) ms
foldr
でたーー!
置換してみよ
> replicateM_' n x = foldr (>>) (return ()) (replicate n x)
foldr
はもうよく分かっているので、replicate
の方をみてみよう
replicate
とは> replicate n x = take n (repeat x)
> take n _ | n <= 0 = []
> take _ [] = []
> take n (x:xs) = x : take (n-1) xs
> repeat x = xs where xs = x:xs
n
の値を知らないとtake
の置換できないので、3
を入れる
replicateM_' 3 x = foldr (>>) (return ()) (replicate 3 x)
replicateM_' 3 x = foldr (>>) (return ()) (x : x : x : [])
(:)
は、(>>)
になる[]
は、return ()
になるreplicateM_' 3 x = x >> x >> x >> return ()
x
を置換するreplicateM_' 3 x = x >> x >> x >> return ()
where x = putStrLn ("Hello " ++ name)
> main'' =
> putStrLn "What is your name?" >>
> getLine >>= \name ->
> putStrLn ("Hello " ++ name) >>
> putStrLn ("Hello " ++ name) >>
> putStrLn ("Hello " ++ name) >>
> return ()
疲れた。
n
の値を知らないとtake
の置換できないので、3
を入れる
と書くとイラっとする。
また、今の証明は3
の値としか証明できてない。 4
を入れるとまた証明が必要。
もっといい方法あるのでしょうか?
数学では、分配法則という法則がある。
\[ \begin{align} a \times (b + c) &= a \times b + a \times c \\ (a \times b) + c &= a \times c + b \times c \end{align} \]
replicateM_
に似たような法則を証明できたら、 何の数字を入れても、方程式を「1」について書き直せる。
replicateM_ 0 x = return () -- 和
replicateM_ (m + n) x = replicateM_ m x >> replicateM_ n x
replicateM_ 1 = id -- 積
replicateM_ (m * n) = replicateM_ m . replicateM_ n
replicateM_ 3 x = replicateM_ (1 + 1 + 1 + 0) x
= replicateM_ 1 x >> replicateM_ 1 x >> replicateM_ 1 x >> return ()
= x >> x >> x >> return ()
where x = putStrLn ("Hello " ++ name)
どうやって証明する?
replicateM_ n x = sequence_ (replicate n x)
この方程式を見ると、分配法則を証明する方法は明らかでない。
ただ、プログラミング的に考えると当たり前でしょう。
replicate
は、ある値をリストにx
回重ねるsequence_
は、あるリストをモナド的に順序で行えるreplicate
にreplicateM_
の「分配法則」に似たような法則を使って、 sequence_
でその法則の「順序化」できるのでしょうか?
replicate
の「分配法則」
replicate 0 x = [] -- 和
replicate (m + n) x = replicate m x ++ replicate n x
replicate 1 = return -- 積
replicate (m * n) = replicate m <=< replicate n
sequence_
の「順序化」
sequence_ [] = return () -- 連結 → 順序制御
sequence_ (xs ++ ys) = sequence_ xs >> sequence_ ys
sequence_ . return = void . id -- リスト関数 → 普通関数
sequence_ . (f <=< g) = (sequence_ . f) . (sequence_ . g)
replicateM_
の「分配法則」の証明
replicateM_ 0 x = sequence_ (replicate 0 x)
= sequence_ []
= return ()
replicateM_ (m + n) x = sequence_ (replicate (m + n) x)
= sequence_ (replicate m x ++ replicate n x)
= sequence_ (replicate m x) >> sequence_ (replicate n x)
= replicateM_ m x >> replicateM_ n x
この方程式は証明できた
replicateM_ 0 x = return ()
replicateM_ (m + n) x = replicateM_ m x >> replicateM_ n x
replicateM_ 1 = id
replicateM_ (m * n) = replicateM_ m . replicateM_ n
ただ、その証明をするため、下記の法則を使った
replicate 0 x = []
replicate (m + n) x = replicate m x ++ replicate n x
replicate 1 = return
replicate (m * n) = replicate m <=< replicate n
sequence_ [] = return ()
sequence_ (xs ++ ys) = sequence_ xs >> sequence_ ys
sequence_ . return = void . id
sequence_ . (f <=< g) = (sequence_ . f) . (sequence_ . g)
これも証明しようと思ったら、逆に証明しないといけない方程式は2倍増えた!