Clojure 模拟图灵机
最近在读《计算的本质:深入剖析程序和计算机 》,一本关于计算理论的小册子,使用 Ruby 语言介绍计算理论,第一步分从状态机开始直接,DFA/NFA、自动下推机直到图灵机,并且每个小章节都给出了代码例子。第二部分开始介绍 lambda 、丘奇数、停机问题等函数式编程的基础知识,挺好玩的一个阅读过程。
作者提供的 Ruby 代码在这里。
我试着用 Clojure 重新实现了里面的图灵机模拟器的例子,
(ns cljcomputionbook.tm
  (:require [clojure.string :as cs]))
;;磁带
(defrecord Tape [left middle right blank]
  Object
  (toString [tape]
    (pr-str tape)))
(defmethod print-method Tape [tape writer]
  (.write writer (format "#<Tape %s(%s)%s>"
                         (cs/join (:left tape))
                         (:middle tape)
                         (cs/join (:right tape)))))
(defn write [{:keys [left right blank]} ch]
  (Tape. left ch right blank))
(defmulti move-head (fn [tape direction] direction))
(defmethod move-head :left [{:keys [left middle right blank]} _]
  (Tape.
   (butlast left)
   (or (last left)
       blank)
   (concat [middle] right)
   blank))
(defmethod move-head :right [{:keys [left middle right blank]} _]
  (Tape.
   (concat left [middle])
   (or (first right)
       blank)
   (next right)
   blank))
;;配置格子
(defrecord TMConfiguration [state tape])
(defprotocol Rule
  (applies-rule? [this conf])
  (follow-rule [this conf]))
(defn- next-tape [tape write_character direction]
  (->
   tape
   (write write_character)
   (move-head direction)))
;;规则
(defrecord TMRule [state character next_state write_character direction]
  Rule
  (applies-rule? [this conf]
    (when (and
           (= state (:state conf))
           (= character (-> conf :tape :middle)))
      this))
  (follow-rule [this conf]
    (TMConfiguration.
     next_state
     (next-tape (:tape conf) write_character direction))))
(defprotocol Rulebook
  (next-configuration [this conf])
  (rule-for [this conf])
  (applies-to? [this conf]))
(defrecord DTMRulebook [rules]
  Rulebook
  (next-configuration [this conf]
    (follow-rule
     (rule-for this conf)
     conf))
  (rule-for [this conf]
    (some
     #(applies-rule? % conf)
     rules))
  (applies-to? [this conf]
    ((comp not nil?)
     (rule-for this conf))))
;; 图灵机模拟器
(defrecord DTM [current_configuration accept_states rulebook debug])
(defn accepting? [{:keys [accept_states current_configuration]}]
  (boolean
   (some (partial = (:state current_configuration))
         accept_states)))
(defn stuck? [{:keys [rulebook current_configuration] :as tm}]
  (and
   (not (accepting? tm))
   (not
    (applies-to? rulebook
                 current_configuration))))
(defn- debug-tm [{:keys [current_configuration debug] :as tm}]
  (when debug
    (println "DEBUG: "
             (merge
              (select-keys current_configuration [:state :tape])
              {:accepting? (accepting? tm)
               :stuck? (stuck? tm)}))))
;;单步执行
(defn step [{:keys [current_configuration accept_states rulebook debug]
             :as tm}]
  (debug-tm tm)
  (DTM.
   (next-configuration rulebook current_configuration)
   accept_states
   rulebook
   debug))
;;模拟运行,直到 accept 或者 stuck
(defn run [tm]
  (if (or (accepting? tm)
          (stuck? tm))
    (do
      (when (:debug tm)
        (debug-tm tm))
      tm)
    (recur
     (step tm))))
然后编写一个递增二进制数字的规则,就可以模拟运行了:
;;定义递增规则
(def rulebook
  (DTMRulebook.
   [(TMRule. 1 0 2 1 :right)
    (TMRule. 1 1 1 0 :left)
    (TMRule. 1 '_ 2 1 :right)
    (TMRule. 2 0 2 0 :right)
    (TMRule. 2 1 2 1 :right)
    (TMRule. 2 '_ 3 '_ :left)]))
;;初始磁带,初始值为二进制 0b1011
(def tape (Tape. [1 0 1] 1 [] '_))
;;运行模拟器
(let [dtm (DTM. (TMConfiguration. 1 tape)
                [3]
                rulebook
                true)
      ran-dtm (run dtm)]
  ;;是否到达接受状态
  (println (accepting? ran-dtm)))
Debug 输出:
DEBUG:  {:stuck? false, :accepting? false, :tape #<Tape 101(1)>, :state 1}
DEBUG:  {:stuck? false, :accepting? false, :tape #<Tape 10(1)0>, :state 1}
DEBUG:  {:stuck? false, :accepting? false, :tape #<Tape 1(0)00>, :state 1}
DEBUG:  {:stuck? false, :accepting? false, :tape #<Tape 11(0)0>, :state 2}
DEBUG:  {:stuck? false, :accepting? false, :tape #<Tape 110(0)>, :state 2}
DEBUG:  {:stuck? false, :accepting? false, :tape #<Tape 1100(_)>, :state 2}
DEBUG:  {:stuck? false, :accepting? true, :tape #<Tape 110(0)_>, :state 3}
true
二进制数据从 1011 递增为 1100 了。