素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

継続、ジェネレータ

だいたい感覚が掴めた。要するに、計算途中のまま一旦ブロック/関数を終了させて、あとで途中から計算を再開できるようにするための仕組みが、継続。計算するものを残したまま終了させれば、returnの代わりになるし、下のようにすれば、PythonのGeneratorを実現できる。

def generator
  esc = nil
  save = lambda {
    yield(lambda {|arg|
      callcc {|cc|
        save = cc
        esc.call(arg)
      }
    })
  }
  lambda {|*args|
    callcc {|cc|
      esc = cc
      save.call(*args)
      raise "StopIteration"
    }
  }
end

g = generator {|y|
  0.upto(2, &y)
  y.call(:end)
}

puts g.call # => 0
puts g.call # => 1
puts g.call # => 2
puts g.call # => :end
puts g.call # => RuntimeError: StopIteration

追記:Python風にクラスを定義すると、下のような感じになる。

class Generator
  include Enumerable

  def initialize(&block)
    @block = block
    @closed = false
    @save = lambda { instance_eval(&block) }
  end

  def next
    send
  end

  def send(val = nil)
    if @closed
      raise Generator::StopIteration
    else
      callcc {|@escape|
        @save[val]
        @closed = true
        raise Generator::StopIteration
      }
    end
  end

  def close
    @closed = true
  end

  def yield(val = nil)
    callcc {|@save| @escape[val] }
  end

  def each
    begin
      self.next
      loop { yield(send) }
    rescue Generator::StopIteration
    end
  end

  class StopIteration < StandardError; end
end

g = Generator.new {
  puts :first
  puts self.yield :second
  puts self.yield :third
}

g.next # => :first
g.send(:val) # => :val, :second
g.next # => nil, Generator::StopIteration: Generator::StopIteration