エンジニアのソフトウェア的愛情

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

リハビリがてらにlis.pyをlis.rbに書き直してみた

すっかりプログラミングにご無沙汰してしまっていました。あっというまに腕がなまりました。気の利いたコードがすっとでてこない。けっこう恐怖です。

そんなわけでリハビリ中です。


最近見つけた記事。

これを他言語へ翻訳。

すでにObjective-Cに翻訳されている方もいらっしゃいますが。

構造が大きく違う言語への翻訳は、まだちと荷が重すぎそうなので、まずはRubyに翻訳してみました。Rubyなのでlis.rb。小文字で書くと見た目の印象が違いますが、大文字にすればLIS.PYとLIS.RBでちょっとは似てませんか?似てませんか…そうですか。

それはそれとして。

lis.pyではmathモジュールを取り込んでいますが、スマートな方法が思いつかなかったので今回はMathモジュールの取り込みは省略してます。それ以外はほとんど直訳。
こちらにも格納:lisp by ruby · GitHub

Global_env =
{
    '+'       => :+,
    '-'       => :-,
    '*'       => :*,
    '/'       => :/,
    'not'     => :not,
    '>'       => :>,
    '<'       => :<,
    '>='      => :>=,
    '<='      => :<=,
    '='       => :==,
    'equal?'  => :==,
    'eq?'     => :instance_of?,
    'length'  => :length,
    'cons'    => Proc.new { |x, xs| [x] + xs },
    'car'     => Proc.new { |xs| xs[0] },
    'cdr'     => Proc.new { |xs| xs[1..-1] },
    'append'  => :+,
    'list'    => Proc.new { |*xs| xs },
    'list?'   => Proc.new { |xs| xs.instance_of? Array },
    'null?'   => :empty?,
    'symbol?' => Proc.new { |x| x.is_a? String }
}

def eval(x, env = Global_env)
  if x.instance_of? String
    env[x]
  elsif not x.instance_of? Array
    x
  elsif x[0] == 'quote'
    _, exp = x
    exp
  elsif x[0] == 'if'
    _, test, conseq, alt = x
    eval(if eval(test, env) then conseq else alt end, env)
  elsif x[0] == 'set!'
    _, var, exp = x
    raise "undefined variable" unless env.key?(var)
    env[var] = eval(exp, env)
  elsif x[0] == 'define'
    _, var, exp = x
    env[var] = eval(exp, env)
  elsif x[0] == 'lambda'
    _, vars, exp = x
    Proc.new { |*args| eval(exp, Hash[vars.zip(args)].merge(env)) }
  elsif x[0] == 'begin'
    result = nil
    x[1..-1].each { |exp| result = eval(exp, env) }
    result
  else
    exps = x.map{ |exp| eval(exp, env) }
    fn = exps.shift
    if fn.instance_of? Proc
      fn.call(*exps)
    else
      op1 = exps.shift
      op1.method(fn).call(*exps)
    end
  end
end

def read(s)
  read_from tokenize(s)
end

def tokenize(s)
  s.gsub('(', ' ( ').gsub(')', ' ) ').split
end

def read_from(tokens)
  raise 'unexpected EOF while reading' if tokens.empty?
  token = tokens.shift
  if token == '('
    l = []
    while tokens[0] != ')'
      l.push read_from(tokens)
    end
    tokens.shift
    l
  elsif token == ')'
    raise 'unexpected'
  else
    atom(token)
  end
end

def atom(token)
  begin
    Integer(token)
  rescue
    begin
      Float(token)
    rescue
      token
    end
  end
end

class Array
  def to_s
    '(' + join(' ') + ')'
  end
end

def repl(prompt = 'lis.rb> ')
  while true
    print prompt
    val    = read(STDIN.gets)
    result = eval(val)
    print "#{result}\n"
  end
end

repl if __FILE__ == $0


実行結果。

 $ ruby lis.rb
 lis.rb> (+ 1 2)
 3
 lis.rb> (- 2 3)
 -1
 lis.rb> (* (+ 3 4) (/ 9 3))
 21
 lis.rb> (quote (1 2 3))
 (1 2 3)
 lis.rb> (< 1 2)
 true
 lis.rb> (> 1 2)
 false
 lis.rb> (list 1 2 3)
 (1 2 3)
 lis.rb> (car (quote (1 2 3)))
 1
 lis.rb> (cdr (quote (1 2 3)))
 (2 3)
 lis.rb> (cons 1 (quote (2 3)))
 (1 2 3)
 lis.rb>  (define x2 (lambda (x) (* x 2)))
 #
 lis.rb> (x2 10)
 20
 lis.rb> 

lambda式の値がProcクラスのオブジェクトそのものとして見えてしまっているのはご愛嬌ということで。

もっとすっきり書ける気がするのでもうちょっと手を加えるかも。

「いつか読むはずっと読まない」:本読みもリハビリを

リハビリといえば。

最近読む本と言えば、ビジネス書、実用書、ノンフィクションばかりで、小説をほとんど読まなくなってしまいました。最近読んだ小説と言ったら「創世の島」だから、半年ぐらい前?さらにその前が「彷徨える艦隊〈5〉戦艦リレントレス (ハヤカワ文庫SF)」なので一年前。これでは頭が栄養不足。
今年は小説ももっと読んで、こっちもリハビリしようかと。
ということで。リハビリでイーガンに手を着けてみる。

万物理論 (創元SF文庫)

万物理論 (創元SF文庫)

創世の島

創世の島

彷徨える艦隊〈5〉戦艦リレントレス (ハヤカワ文庫SF)

彷徨える艦隊〈5〉戦艦リレントレス (ハヤカワ文庫SF)