Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

Racket + big-bangを使ってどうしようもないプログラムを書く方法

この記事はLisp Advent Calender 13日目の筈でした。

はじめに: プログラミングの楽しみ

さて、Lisp処理系と呼ばれるものには様々なものが存在します。その中で、そこそこに人気のある処理系であるRacketを使って、Lispと一緒に気軽に戯れてみよう!というのが今回の趣旨です。

まず最初に、プログラミングをする上での楽しみというのは一体何でしょうか。当たるようなサービスを作る? それとも仕事で効率化するようなものを作る? それとも、様々なアルゴリズムについての洞察を得たりする? どれも、プログラミングの楽しみであるのは間違いないし、否定することはできません。しかし、プログラミングの楽しみとは、別の意味では「ゴミみたいなプログラム」を書くことではないでしょうか。つまり、「どうしようもないプログラム」を作ってゲラゲラ笑うといったことです。

例えば、とある中級者のプログラマが書いた記事によれば、彼が楽しかったのはF**kジェネレーターというものです。こういったどうしようもないものを気軽に楽しめるというのは、プログラムを嗜む上での重要なことかと思います。

なので、今回の記事では、Racketによって「ファックジェネレーター」を作ってみるというのが、その趣旨となります。

なぜDr.Racketなのか

ここで多種あるLisp処理系の中で、なぜRacketなのかというと、このRacketについているDr.Racketという実行環境が、一発で入り、かつ割と親切な構造になっているからです。例えば、マウスを関数の上に置くと、次のように、関数の定義を追いかけて、どこらへんでエラーが起きているのかのヒントとすることができたりします。

f:id:nisemono_san:20141214193629p:plain

また、このRacketの標準的なテキストとして、How to Design Programという英語の本が無料で閲覧できるのですが、中身がふるっていて、以下のような定義がいきなり出てきます。

f:id:nisemono_san:20141214193642p:plain

もしかしてDr.Racketって、コードに画像を埋め込めるのでは……と思って、試してみると、ビンゴ、確かにそうなのです。

sigeru

そういえば、画像リテラル、色リテラルが導入されていたら?という議論がありましたが、Dr.Racketに関して言うなら、それを半分くらい実現しているというわけです。面白い!

big-bangを使ってみよう

そこで、私たちはファックをランダムで表示させるという愉快なコードを書きたい、という野望がありました。そこで、Racketにはbig-bangという関数を利用すると、そのあたりが簡単にできます。さらにいうと、big-bangは、非常に教育的な関数で、ちょっと面白かったりもします。

例えば、ここでイテレータブルなFizzBuzzを定義したいと考えるとしましょう。イテレータブルなFizzBuzzとは、fizzbuzz-nextという関数によって、いわゆるFizzBuzzのルールによって出力されるウィンドウを生成するということです。とりあえず、実際のコードを読みながら解説していったほうが早いので、そのようにします。

#lang racket
(require test-engine/racket-tests)
(require 2htdp/image)
(require 2htdp/universe)

;; FizzBuzz Structure
;; Number Boolean String
(struct fizzbuzz (n is str))

;; Default FizzBuzz
(define (start-fizzbuzz) (fizzbuzz 1 false ""))

;; Number -> FizzBuzz
(define (number->fizzbuzz x)
  (fizzbuzz-next (fizzbuzz (- x 1) false "")))

; FizzBuzz iterator
(define (fizzbuzz-next fiz-struct)
  (let ([next-n (+ (fizzbuzz-n fiz-struct) 1)])
    (cond 
      [(= (modulo next-n 15) 0) (fizzbuzz next-n true "FizzBuzz")]
      [(= (modulo next-n 3)  0) (fizzbuzz next-n true "Fizz")]
      [(= (modulo next-n 5)  0) (fizzbuzz next-n true "Buzz")]
      [else (fizzbuzz next-n false empty)])))

(define (fizzbuzz-output fizz-struct)
  (if (fizzbuzz-is fizz-struct)
      (fizzbuzz-str fizz-struct)
      (number->string (fizzbuzz-n fizz-struct))))

(define (fizzbuzz-action state key)
 (cond [(string=? "j" key) (fizzbuzz-next state)]
       [else state]))

(define (fizzbuzz-universe initial-state)
  (big-bang initial-state
            (on-key fizzbuzz-action)
            (to-draw fizzbuzz-render)))

(define (fizzbuzz-render state)
  (overlay/align "middle" "middle"
                 (text (fizzbuzz-output state) 40 "blue")
                 (empty-scene 300 100)))

このコードは、以下のコマンドで実行できます。

(fizzbuzz-universe (number->fizzbuzz 1))

fizzbuzz-iter

このコードで重要な点は、まずFizzBuzzにおいて、現在呼び出されている関数がいかなる順番であるのかという状態です。racketの場合は、状態をstructで定義します。

(struct foo (a b c))

このstructは、呼び出す際にfoo-afoo-bといった感じで呼び出すことが可能になります。

さて、細かい部分に関しては見るところがあります。例えば、racketは文字列と数字の比較を明確に区別しているとか、そういうところは結構「そういえば意識していなかったな」と思わせるところではあったりもします(当然といえば当然なのですが)が、今回の趣旨はbig-bangの使い方です。あえてその場所を抜き出してみましょう。

(define (fizzbuzz-universe initial-state)
  (big-bang initial-state
            (on-key fizzbuzz-action)
            (to-draw fizzbuzz-render)))

ここで重要なのは、big-bangは、あるイベントによって状態を受け取ってその都度評価しているということです。この場合だと、on-keyによって生成されたイベントを受け取り、その結果をto-drawに渡してウィンドウを生成し、そして次に渡しているわけです。

ここで考えるので面白いのは、状態というのは、ある関数内で次の関数にその状態を丸投げすることで、別段グローバル変数を使わなくても実現できるということです。実際、この関数というのは2htdp/universeというパッケージで定義されているわけですが、この定義を見てみると、面白い図が載っています。

f:id:nisemono_san:20141214194201p:plain

Racket Documentationからの引用ですが、このように次々に状態を渡してあげることにより、「連続的な状態」を作り出しているというわけなのです。

F**k Lisp

私たちの課題は最初に述べたように、F**kを次々と表示するウィンドウを生成することです。口で長々と説明しても仕方ないかと思うので、先にそのコードを見てましょう。

#lang racket
(require 2htdp/image)
(require 2htdp/universe)

(define CANPAS-WIDTH 300)
(define CANPAS-HEIGHT 300)
(define MIN-FONT 16)
(define MAX-FONT 64)

(define now-image
  (empty-scene CANPAS-WIDTH CANPAS-HEIGHT))

(define (fuck-place x y font-size color campas)
  (place-image 
                   (text "ファック" font-size color)
                   x y
                   campas))


(define COLOR-LIST
  '("white" "white" "red" "blue" "blue" "yellow" "green" "green" "maroon" "black"))

(define (random-color)
  (car (shuffle COLOR-LIST)))

(define (random-fuck campas)
  (fuck-place 
                       (random CANPAS-HEIGHT)
                       (random CANPAS-WIDTH)
                       (+ MIN-FONT (random MAX-FONT))
                       (random-color)
                       campas))

(define (fuckintosh) 
  (big-bang now-image
            [to-draw random-fuck]
            [on-tick random-fuck]))

これを実行すると、下のようになります。

fuckintosh

先ほどのやたらとややこしいコードはなんだったのだ、という感じなんですが、ポイントは状態といったときに、それは画像も含まれているよ、ということです。なので、画像を次々と渡していき、それに対してファックという文字列を上から書き込んであげればいい、というわけです。

さて、ここに新しくon-tickという関数が出てきました。先ほどの場合だと、on-keyをトリガーにしてイベントが発生しましたが、on-tickとは何でしょうか。これは簡単な話で、つまりウィンドウの中では、ループが走っていて、そのループが走ったタイミングで実行されるという関数なわけです。

なんだかややこしく見えるんですけど、実際こういう挙動を処理系自体が持っている場合があります。そう、node.jsです。もちろん、単純に一緒とは言えないのですが、しかし発想自体は多く似ている部分がある気はします。

悪いところ

上のコードはrender部分と画像生成部分、つまりrandom-fuckを二回の画面で呼び出しているので、巻き戻りが発生している部分もあり、そこは改善点です。

もっとbig-bang!

そこで、big-bangには一つだけ欠点があります。それは、いわゆるJavaScriptにおけるsetIntervalみたいなものが存在していないことです。これは、前にもリンクしたHow to Design Programの演習にも課題としてとりあげられています。左のリンクのA door-simulation programのところですね。ループの回数を記憶して、ある回数になったら、その関数が評価されるみたいな、ヘルパー関数を用意するコードを書いてみるといいかもしれません。ちなみに、自分の下手な定義だと、下のようなコードを用意したりしていました。

(define tick-time 0)
(define (tick-trigger anyfunction x)
  (cond [(> tick-time 10) (set! tick-time 0) (anyfunction x)]
        [else (set! tick-time (+ tick-time 1)) x ]))

これが下手なのは、関数の分だけtick-timeを用意しなければいけない実装になっているので、本来ならば、それを避けたほうが、よりスマートな定義かと思われます。

終わりに

というわけで、今回はRacketの魅力及びその中のbig-bangの関数について解説しました。こんな単純なコードですら、色々な洞察が得られて楽しかったし、それらの洞察が決してLisp内部だけではなく、他のプログラムにおいても似たような発想が採用されているんだなというのが透けてたのしかったです。是非、皆さんもDr.Racketで遊んでみましょう。

Next Lisper

common-lisp - 証明された Common Lisp / Emacs Lisp コードを手に入れる - Qiita