Rubyでパターンマッチが使えるライブラリを作ってみた。

Rubyには、配列などから値を取り出したりするパターンマッチの機能はありません。
しかし、時々欲しいと思うことがあります。そんな時のために作ってみました。
(実は自分でもなんで作ったんだろうと思うような時もあるのですが)
githubで公開しています。https://github.com/tana/ruby-pattern-match

使い方

match.rbをrequireなどで読み込んだら、

match [1, 2, 3] do
  pat [:a, :b, :c] do
    a + b + c
  end
end

のように使えます。
条件分岐でマッチするかしないかを分ける場合、

match [1, 2] do
  pat [:a, :b], cond { b == 2 } do
    a
  end
end

のように、condをパターンの後に付ければ条件を付けることができます。
配列だけではなくStructにも、

Aaaaa = Struct.new(:a, :b)
match Aaaaa[1, 2] do
  pat Aaaaa[:a, :b] do
    a + b
  end
end

のように、値を取り出すことができます。
その他にもいろいろな機能がありますが、test.rbにはいくつか例が書いてあります。

HTML5のCanvasを使って方眼紙みたいな画像を生成するプログラムを作ってみた。

HTML5Canvasを使えば、Javascriptでも画像を生成するプログラムが簡単に作れます。
縦と横に線が並んでいて、線と線が交わるところに円が書かれた画像が欲しかったので、
多分同じことをするソフトはすでに存在すると思いますが、HTML5で作ってみました。

こんな画像を作成できます。

http://tana.pv.land.to/masume.html で使えます。
レンタルサーバーを使っているので、広告で見にくいかもしれません。
元のhtmlファイルは https://docs.google.com/leaf?id=0B4F8plB7iQBoZWI1NDg3MjUtNzU4Ni00ZDNkLTk2ZTctNmE4Y2UwNTY0MWI2&hl=ja にアップロードしてあります。

円の半径や、縦と横の円の数などの数字を入力して、線の有無や円を塗りつぶすかを選んで「作成」をクリックすれば、画像が作成されます。
PNG画像を生成」をクリックすると、新しいタブが開いて、PNG画像が表示されるので、保存などができるようになります。

canvasは便利ですが、アンチエイリアスが解除できないのが少し問題かもしれません。

パターンマッチみたいなことをするプログラムを書いてみた。

Common Lispです。
パターンマッチといっても、束縛する値のリストを連想リストとして返すだけなので、プログラムに応用するにはもう少し工夫が必要かもしれません。
できるだけ簡潔に書こうと思っていましたが、結局長くなってしまいました。

(defun zip-with (f a b)
  (if (or (null a) (null b))
    '()
    (cons (funcall f (car a) (car b)) (zip-with f (cdr a) (cdr b)))))

(defun any (f l)
  (if (null l)
    nil
    (if (funcall f (car l))
      t
      (any f (cdr l)))))

(defun flatten (x)
  (cond ((null x) nil)
        ((atom x) (list x))
        ((and (listp x) (not (listp (cdr x)))) (list x))
        (t (append (flatten (car x)) (flatten (cdr x))))))

(defun match-value (pat data)
  (cond ((symbolp pat)
         (cons pat data))
        ((and (atom pat) (atom data))
         (if (eql pat data) nil 'fail))
        ((and (listp pat) (listp data))
         (if (= (length pat) (length data))
           (zip-with #'match-value pat data)
           'fail))
        (t
          'fail)))

(defun match (pat data)
  (let ((result (match-value pat data)))
    (if (not (listp result))
      'fail
      (if (any (lambda (x) (eql 'fail x)) result)
        'fail
        (flatten result)))))

コードの説明

必要になったので、リスト操作関係の関数をいくつか作っています。もしかしたら標準ライブラリにもあったかもしれません。
関数zip-withは、二つのリストのそれぞれの要素に関数を適用して一つのリストを作る関数です。
anyは、リストの中に一つでも条件に合う要素があればt、一つも無ければnilを返す関数です。
flattenは少しだけオリジナル仕様です。ネストしたリストを平坦なリストにしますが、cdrがリストではないドット対はそのままにします。

match-valueがパターンマッチの本体ですが、返すリストはそのままでは使えないので、
match関数で連想リストに直しています。

使う

* (match '(1 2 a) '(1 2 3))

((A . 3))
* (match '(1 2 3) '(1 2 3))

NIL
* (match '(1 2 3) '(4 5 6))

FAIL
* (match '(1 (2 a) b (c d 3)) '(1 (2 1) 2 (3 4 3)))

((A . 1) (B . 2) (C . 3) (D . 4))

こんな感じです。成功した場合は連想リストかnil、失敗した場合はfailを返します。

追記

「(match '(a b) '(1 (2 3)))」のように、データのほうがネストしている場合に変なことになるというミスがありました。今度修正します

GtkとWnckを使って、ウィンドウのリストを取得する。

作りたいものを思いついたので、Wnckについてもう一度、少し調べてみました。
今回は、その方法の実験として書いたプログラムです。
ウィンドウのタイトルと、ウィンドウを開いているアプリケーションの名前を取得します。
アプリケーションの名前は何なのかはよくわかりませんが、実行ファイル名だったり、わかりやすい名前だったり、いろいろな場合があるようです。

こんな感じに表示されます。

# -*- coding: utf-8 -*-
import gtk
import wnck

win = gtk.Window()

tv = gtk.TreeView()

col1 = gtk.TreeViewColumn("ウィンドウ名", gtk.CellRendererText(), text=1)
col2 = gtk.TreeViewColumn("アプリケーション名", gtk.CellRendererText(), text=2)

tv.append_column(col1)
tv.append_column(col2)

ls = gtk.ListStore(int, str, str)
tv.set_model(ls)

sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add(tv)

screen = wnck.screen_get_default()
screen.force_update()
count = 1
for w in screen.get_windows():
  ls.append([count, w.get_name(), w.get_application().get_name()])
  count = count + 1

win.connect('delete_event', gtk.main_quit)

win.add(sw)

win.set_size_request(640, 240)
win.show_all()
gtk.main()

Gtkでリストを作るところは、今までやったことがなかったので少し難しかったです。
23行目の「screen.force_update()」を入れないと、ウィンドウリストを取得するところで空リストが返ってきてしまうようです。

Lispbuilder-SDLのインストール

Lispでゲームを作りたいと思い、UbuntuLispbuilder-SDL(Common LispからSDLを使うライブラリ)をインストールしてみました。
今回はその手順を書いておきます。
Common Lisp処理系には、apt-getでインストールしたSBCLを使っています。

準備

Lispbuilderでは、Common LispからC言語のライブラリを使うためにcffiというライブラリを使っているのでそれをインストールします。

sudo apt-get install cl-cffi

Ubuntuならパッケージからインストールできます。

インストール

Lispbuilder本体をインストールします。Common Lispではパッケージのインストールに、ASDFというソフトを使います。
ASDFRubyでいうgemのようなものらしいです。SBCLをインストールした時にASDFも一緒にインストールされているはずです。
ASDFはパッケージを自動でダウンロードする機能を持っていますが、ASDFに登録されているダウンロード先が404になってしまっているようなので、Lispbuilderの公式サイトからファイルをダウンロードしてインストールします。
http://code.google.com/p/lispbuilder/ の「Featured downloads」のところから lispbuilder-sdl-0.9.8.1.tgz をダウンロードしようとしましたがなぜかうまくいかないので、

wget http://lispbuilder.googlecode.com/files/lispbuilder-sdl-0.9.8.1.tgz

として直接ホームディレクトリにダウンロードしました。

次に、

sudo sbcl

で、rootとしてSBCLを起動します。今回はホームディレクトリ以下ではなく/usr/lib/sbcl/siteにインストールするのでroot権限が必要になります。
起動したら、

(require 'asdf-install)

(asdf-install:install "lispbuilder-sdl-0.9.8.1.tgz")

でインストールを始めます。
まず、Install where?と聞かれます。今回はSystem-wideを選択したので、「1」と入力しエンターを押しました。
「compiling」と書かれたメッセージがたくさん出てくるのでしばらく待ちます。
途中、「debugger invoked on a ASDF-INSTALL::KEY-NOT-FOUND」というエラーが何度か出ますが、「0」と入力してエンターを押せばそのまま先に進めます。

インストールが完了し、「* 」と表示されてREPLに戻ったら、

(sb-ext:quit)

SBCLを終了します。

サンプルを動かしてみる

曲線を引くサンプルを動かしてみます。
普通に

sbcl

SBCLを起動し、

(asdf:operate 'asdf:load-op :lispbuilder-sdl-examples)

でLispbuilder-SDLとサンプルを読み込みます。少し時間がかかります。

(sdl-examples:bezier)

でサンプルを起動します。曲線がいくつか出てきたら成功です。

Lispbuilder-SDLを使ったサンプルプログラム

マウスで画像を動かすサンプルを書いてみました。

(defvar *image* nil)
(sdl:with-init ()
  (sdl:window 320 240 :title-caption "Lispbuilder-SDLサンプル")
  (setf (sdl:frame-rate) 30)
  (setf *image* (sdl:load-image "lisp.bmp"))
  (sdl:update-display)
  (sdl:with-events ()
    (:quit-event () t)
    (:video-expose-event () (sdl:update-display))
    (:idle ()
      (sdl:clear-display sdl:*black*)
      (sdl:draw-surface-at-* *image* (sdl:mouse-x) (sdl:mouse-y))
      (sdl:update-display))))

ソースコードのファイル名は、今回はsdltest.lispというファイル名にしています。
lisp.bmpは、適当なbmpファイルを使います。
実行は、sbclを起動して

(require 'lispbuilder-sdl)
(load "sdltest.lisp")

です。
ライブラリを読み込むのには少し時間がかかります。マウスの位置に合わせて画像が動きます。
sdl:with-initやsdl:frame-rateなど、そのままのSDLよりも使いやすいように作られているようです。

Rubyで二つのGdk::Pixbufを重ねて新しいGdk::Pixbufを作る。

GTKで画像を扱う時に使われるGdk::Pixbufを重ねて新しいGdk::Pixbufを作る方法を書いてみます。
背景画像の上に重なる画像には、背景が透過した画像を使います。
画像は二つとも、一辺が256ピクセルの正方形になっていることを想定して書いたので、256という数字を書き換えればいろいろなサイズに対応できると思います。
今回は「hoge.png」という名前で保存していますが、普通は画面に描画すると思います。
http://lethalman.blogspot.com/2009/04/create-pixbuf-from-cairo-surface.html ここを参考にしました。

require 'gtk2'
require 'cairo'

pict1 = Gdk::Pixbuf.new("背景画像")
pict2 = Gdk::Pixbuf.new("上に重ねる画像")

pixmap = Gdk::Pixmap.new(nil, 256, 256, 24)
ctx = pixmap.create_cairo_context
ctx.set_source_pixbuf(pict1)
ctx.paint
ctx.set_source_pixbuf(pict2)
ctx.paint

pixbuf = Gdk::Pixbuf.from_drawable(Gdk::Colormap.system, pixmap, 0, 0, 256, 256)

pixbuf.save("hoge.png", "png")

wnckを使ってウィンドウが開かれたことを通知する

wnckというライブラリを使い、ウィンドウが開かれた時にウィンドウタイトルを表示するプログラムを書いてみました。
これはタイトルを表示するだけですが、wnckはウィンドウの位置やサイズを操作することができるようなので、うまく使えばいろいろな応用ができそうです。
本当はRubyでやりたかったのですが、Ruby用のバインディングが見つからなかったのでPythonで書きました。

# -*- coding: utf-8 -*-
import wnck

def opened(screen, win):
  print win.get_name()

screen = wnck.screen_get_default()

screen.connect("window_opened", opened)

raw_input("Enterを押すと終了します")

このようにシンプルに書けます。wnckはGNOME関係で使われているライブラリらしいので、GTKと同じシグナルの仕組みが使えます。

wnckは情報が少ないので、いろいろなソースコードなども調べて書きましたが、日本語の情報があまりないので大変でした。