BQP

最中などいかかでしょうか? 略してMonads

(このエントリはLisp Advent Calendar 9日目の記事です)
Lisp好きで関数型にちょっと興味があり、ちょうど面白そうなチュートリアルを見つけたので、紹介します。

Monads for Schemersというものです。これは関数型の文脈でよく言及されるMonadsについて、Schemeを使い説明していくもので、「なんかHakellとか面白そうだぞ」と思っていた(Lisp advent calendarなのに……!)私にぴったりでした。これの内容をかいつまんで説明します。

副作用はありません

副作用のないSchemeのサブセット(Curry化されている)"Pure Lisp"において次のプログラムを処理することを考えてみましょう。

(define (begin v1 v2) v2)
(begin (turn-on-safety!)
       (pull-trigger!))

ここで気をつけないと行けないのは副作用のない"Pure Lisp"では引数の評価順序に規則がないことです。もしも、引数の評価順序によって例えば今の"begin"の値が変わってしまったとすると、それは副作用があるということで、前提と矛盾します。ですので、引数の評価順序に規則はありません。右からやっても、左からやっても、左右に振動しながらでも構わないはずです。なので、

   (begin (turn-on-safety!)
           (pull-trigger!))
 -> (begin (turn-on-safety!)  ; effect: pull-trigger!
           #<void>)
 -> (begin #<void>            ; effect: turn-on-safety!
           #<void>)
 -> #<void>

というように評価されても構いません。この場合、銃の引き金を引いてからセイフティを外すことになります。

ピュアじゃないとイケナイ世界

この文章内でMonadsを導入する理由を文の後半から引用すると、
1. 評価順序を規定すること。(enforcing evaluation order)
2. アキュムレータを抽象化すること。(abstracting away accumulators)
と書かれています。
途中の説明において、評価順序を規定するのには継続渡しスタイルを、アキュムレータを抽象化するのにはアキュムレータ渡しスタイルが用いられています。
継続渡しスタイルに関しては多くの方が聞いたことくらいはあると思います。わかりやすく短い解説(ただし英語)がありますので(The 90 minutes Scheme to C compiler)こちらを参照してください。
あるいは、ググればたくさんチュートリアルがあるので、頑張ってください。

最後の方に書かれている"pipe"(>>=, *, let)と"lift"(unit, return)を使った、"monadic rand in Lisp"を見てみましょう。乱数生成のためのseedから、生成された乱数とそれをseedとして持ったconsを返すことが目的です。

(define (rand)
  (pipe (get-seed)
        (lambda (seed)
         (let ([ans (modulo (* seed 16807) 2147483647)])
            (begin (set-seed ans)
                   (lift ans))))))

これを、Haskellで書くとこんな感じでしょうか。State Monadがすでに抽象化されている分短いですね(きちんと書けてないけど……)。

import Control.Monad.State
rand :: State Int Int
rand = state $ \x -> (( x * 16807 ) `mod` 2174, (x* 16807) `mod` 2147483647)

何がどうなってこういうコードになるかはさておき、実際にgaucheで動かしてみましょう。他にも"lift"や"set-seed"関数などを本文から拾ってくる必要があります。あと、Curry化されていないことにも気をつけて下さい。

gosh> (define (runState m) (m (date-nanosecond (current-date))))
runState
gosh> (runState (rand))
(799718464 . 799718464)
gosh> (runState (rand))
(765944281 . 765944281)

seedを受け取って、乱数と乱数生成のためのseedのconsが返って来ました。同様にHaskellでも、

*Main> runState (do{rand}) 1652
(1010, 1010)

となります。

おわりに

Lispあんまり関係ないじゃないか(#^ω^)ピキピキという向きもあると思います、というか書いてて思いました。
けど、Lisp、特にSchemeとかだと継続の話題が多いので、こういうのもあるんだなーという感じで面白いと思います。
何に使えるかどうかですか? ……なんでしょうね? :-)