(S-Expression for Ruby) ファンクション倶楽部2008秋イベント LT

ファンクション倶楽部!に参加してきました、イベントのもようは artonさんのブログ に良くまっとまっています。

私は、LT で Ruby用のS式/Lispモジュール (S-Expression for Ruby) の話をさせて頂きました。

S-Expression for Rubyの発表資料は http://www.ey-office.net/public/Sexp4Ruby.pdf に置きました。


S-Expression for RubyRuby上で S式、Lispが書けるようにするモジュールで、RubyLispの間で変数を共有しています。 したがって Rubyの Proc を使って定義した関数を Lisp側で呼び出したり、Lisp側で定義した関数を Ruby の call() を使って呼び出したりできます。
また、Lisp側では (メソッド Rubyオブジェクト1 Rubyオブジェクト2 。。。) は Rubyオブジェクト1.メソッド(Rubyオブジェクト2 。。) として呼び出せるので、かなりのRubyの機能を Lisp側から使えます。

require 's'
S.bind(binding)

sub1 = proc {|n| n - 1} 
fact = s %%(lambda(n) 
             (if (== n 0) 
                 1 
                 (* (fact (sub1 n)) n))) % 
e %% (fact 4) %
fact.call(4) 
e %% (define double (lambda(n) (+ n n)))% 
double.call(3)

Ruby では文字列を %! ・・・・ ! (注 !の所は任意の文字が使える) と表記できるので、 一見S式が文字列に見えないのが ミソ です ^^)
あと binding という関数が S-Expression for Ruby の 肝 です。


id:k3cさんが、是非ソースを公開してほしいとリクエストがあったのですが、LT に合わせ 2日(実質 1日)で書いたコードなので、少しきれいにしてから公開します。

とりあえず、ファンクション倶楽部と言いながらも実はオブジェクト倶楽部のイベントだったので、テスト重要 ということでテストコードを公開しておきます。

#
require 'test/unit'
require 's'

class TestS < Test::Unit::TestCase

  def aa_test_print
    assert_equal "123", S.new(123).to_s
    assert_equal ":a", S.new(:a).to_s

    assert_equal "(:a.:b)", S.new(Cell.cons(:a, :b)).to_s
    l2 = Cell.cons(:a, Cell.cons(:b, nil))
    assert_equal "(:a :b)", S.new(l2).to_s
    assert_equal "(123 :a :b)", S.new(Cell.cons(123, l2)).to_s
  end

  def test_read
    assert_equal "123", S.new("123").to_s
    assert_equal ":a", S.new(":a").to_s
    
    assert_equal "nil", S.new("()").to_s
    assert_equal "(1)", S.new("(1)").to_s
    assert_equal "(1.2)", S.new("(1.2)").to_s
    assert_equal "(n)", S.new("(n)").to_s
    
    assert_equal "(1 2)", S.new("(1 2)").to_s
    assert_equal "(1 2 3)", S.new("(1 2 3)").to_s
    assert_equal "(1 (2 3) (4.5))", S.new("(1 (2 3) (4.5))").to_s
    assert_equal "(lambda (n) (+ n 3))", S.new("(lambda(n) (+   n 3))").to_s
  end

  def test_eval
    S.bind(binding)

    assert_equal 12, (e %% 12 %)
    v1 = 33
    assert_equal 33, (e %% v1 %)
    add = proc {|a,b| a + b}
    assert_equal 7, (e %% (add 3 4) %)
    assert_equal 6, (e %% ((lambda(a b) (add a b)) 2 4) %)

    sum = s %% (lambda(x y) (add x y)) %
    assert_equal 5, (e %% (sum 2 3) %)
    assert_equal 7, (e %% (+ 3 4) %)

    
    sub = proc {|a,b| a - b}
    mul = proc {|a,b| a * b}
    eq = proc {|a,b| a == b}
    fact = s %%(lambda(n)
                 (if (eq n 0)
                     1
                     (mul (fact (sub n 1)) n))) %
    assert_equal 6, (e %% (fact 3) %)
    assert_equal 24, fact.call(4)
    assert_equal 3628800, (e %% (fact 10) %)

    fact2 = s %%(lambda(n)
                  (if (== n 0)
                      1
                      (* (fact2 (- n 1)) n))) %
    assert_equal 120, (e %% (fact2 5) %)

    e %% (define double
            (lambda(n) (+ n n))) %
    assert_equal 6, (e %% (double 3) %)
    assert_equal 8, eval("double.call(4)")
  end


end

たぶん、S-Expression for Ruby解説のエントリーを書きます・・・・・・