--- layout: old_post title: On Lisp「22章 非決定性」をRubyで試してみる permalink: /tatsuya/show/413-on-lisp-22-ruby ---


"On Lisp―Advanced Techniques for Common Lisp" (Paul Graham)

を読んだ。

・Lispの基礎から、マクロ、遅延評価、Schemeと継続、継続をCommonLispで実装して、継続を使った非決定的アルゴリズムの実装、Prolog

初めて関数型プログラミング、というかLispを勉強した身にとっては全編興味深い、特に22章の「非決定性」が面白かった。

On Lisp - 非決定性

非決定的アルゴリズムはある意味では超自然的な予見に基づいて動作するものだ.超能力を持ったコンピュータに触れることのない私達に,どうしてそんなものが必要なのだろうか?\ それは非決定的アルゴリズムを決定的アルゴリズムでシミュレートできるからだ.純粋に関数的なプログラム ---すなわち副作用の一切ないもの--- では,非決定性は特に直截的になる.純粋に関数的なプログラムでは非決定性はバックトラックを用いた探索で実現できる.

バックトラックによる非決定性の実装例が紹介されてる、乱暴に言うと「複数の選択肢がある時、その時点の継続を保存しとりあえず一つの選択肢を選んで処理を続ける。成功すればOK、もし選択が失敗してた場合選択前の過去に戻って、別の選択肢を選んで処理を継続する」で良いのかな。

とりあえず理解のためにRubyで実装してみた。

class Nond
  def initialize
    @paths = []
  end

  def choose(choises)
    if choises.empty?
      failed
    else
      return callcc do |c|
        @paths.unshift(lambda{ c.call( choose(choises[1..choises.length]) ) })
        choises[0]
      end
    end
  end

  def failed
    if @paths.empty?
      raise "Can't find more choises."
    else
      @paths.shift.call
    end
  end
end

Ruby1.8 にも継続があるので、ほぼ本のまま。試してみる。

@nond = Nond.new

def parlor_trick(sum, firsts, secounds)
  first = @nond.choose(firsts)
  secound = @nond.choose(secounds)

  if sum == first + secound
    "The sum of #{first} #{secound}"
  else
    @nond.failed
  end
end

p parlor_trick(7, [1,2,3,4,5], [1,2,3,4])

二組の数字のリストから加算の結果が sum と等しい組み合わせを返す。

30BF30FC30DF30CA30EB 2014 screen 2014

実行してみると確かに3と4で7になってる。

もう一例、今度は名前のリストから "Igor" を見つける

def igor(names)
  name = @nond.choose(names)
  if name=="Igor"
    p "Found Igor!"
  else
    @nond.failed
  end
end

p igor(["Yakov", "Yulli", "Raisa", "Iwan", "Igor", "Daniel", "Egor"])
30BF30FC30DF30CA30EB 2014 screen 2014 170

 

肝の関数 choose と fail の実装を見れば納得できるけど、アプリケーションのコードだけを見るとまるで choose が超能力で正解を導き出したように見える、その実装も「失敗したら過去に戻ってやり直す」てのは凄いな。

この22章非決定性の発展で、24章ではLispの埋め込み言語としてPrologを実装してるけど、ここまでいくと正直理解があいまい。。また読み直そう

On Lisp - Prolog

Prolog面白そうだな、どこから勉強するのが良いんだろ。とっかかりとして。