等式推論

最適化と証明

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 を証明してみよ

もっと一般的に

sumfoldr (+) 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 : [])
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)

この方程式を見ると、分配法則を証明する方法は明らかでない。

ただ、プログラミング的に考えると当たり前でしょう。

replicatereplicateM_の「分配法則」に似たような法則を使って、 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倍増えた!

何の価値?

正確さを証明できること

あるいは、ある法則に従っていることを証明できること

最適化をできること

=最適化されたバージョンは元のバージョンに等しいと証明すること

ドキュメンテーション

Hackageではよく使われている

難しいコンセプトを理解すること

分からないモナドがあったら等式推論をやってみ!

なれたら実は楽しい!

等式推論やってみたい!アドバイスとコツ

参考文献

ありがとうございました