这个Python代码的惯用Clojure等价物?

这个Python代码的惯用Clojure等价物?,python,clojure,Python,Clojure,我用Python编写了一个简单的基于堆栈的虚拟机,现在我正试图用Clojure重写它,这很困难,因为我没有太多的Lisp经验。处理字节码,字节码表示为元组列表,如下所示: [("label", "entry"), ("load", 0), ("load", 1), ("add",), ("store", 0)] 或在Clojure中: [[:label :entry] [:load 0] [:load 1] [:add] [:store 0]] 当一个函数对象加载字节码时,每

我用Python编写了一个简单的基于堆栈的虚拟机,现在我正试图用Clojure重写它,这很困难,因为我没有太多的Lisp经验。处理字节码,字节码表示为元组列表,如下所示:

[("label", "entry"),
 ("load", 0),
 ("load", 1),
 ("add",),
 ("store", 0)]
或在Clojure中:

[[:label :entry]
 [:load 0]
 [:load 1]
 [:add]
 [:store 0]]
当一个函数对象加载字节码时,每个“标签”元组都会被专门处理以标记该位置,而其他每个元组都会保留在最后的字节码中。我假设这个函数的Clojure等价物将涉及折叠,但我不确定如何以优雅或惯用的方式实现这一点。有什么想法吗

(defn make-function [name code]
  (let [[code labels] (reduce (fn [[code labels] inst]
                                (if (= (first inst) :label)
                                  [code (assoc labels (second inst) (count code))]
                                  [(conj code inst) labels]))
                              [[] {}] ;; initial state of code and labels
                              code)]
    {:name name, :code code :labels labels}))

我喜欢它有点宽,但还不算太坏。

阅读该Python代码片段,看起来您希望最终的输出是这样的

{:code [[:load 0]
        [:load 1]
        [:add]
        [:store 0]]
 :labels {:entry 0}}
一旦对目标有了明确的描述,编写代码就容易多了,事实上,这是一个非常简单的简化过程。有许多风格不同的方法来编写简化程序,但对我来说,这种方法似乎最容易阅读

(defn load [asm]
  (reduce (fn [{:keys [code labels]} [op arg1 & args :as instruction]]
            (if (= :label op)
              {:code code
               :labels (assoc labels arg1 (count code))}
              {:code (conj code instruction)
               :labels labels}))
          {:code [], :labels {}},
          asm))
编辑 此版本支持
name
参数,并通过不重复不变的元素简化了缩减步骤

(defn load [name asm]
  (reduce (fn [program [op arg1 :as instruction]]
            (if (= :label op)
              (assoc-in program [:labels arg1] (count (:code program)))
              (update-in program [:code] conj instruction)))
          {:code [], :labels {}, :name name},
          asm))

我不能保证这是惯用的Clojure,但这是Python代码的一个功能版本,至少应该让您非常熟悉

(def prog [
 [:label :entry]
 [:load 0]
 [:load 1]
 [:add]
 [:store 0]])

(defn parse [stats]
    (let [
        f (fn [[out-stats labels pc] stat]
            (if (= :label (first stat))
                [out-stats (conj labels [(second stat) pc]) pc]
                [(conj out-stats stat) labels (+ 1 pc)]))
        init [[] {} 0]
        ]
        (reduce f init stats)))

(println (parse prog))
所以我认为你是对的,折叠是你想要的。所有功能折叠遍历一个集合,并将该集合“减少”为单个值。但是,没有任何内容表明,生成的单个值不能同时是集合或集合的集合(如本例所示)

在我们的例子中,我们将使用reduce的三参数版本-这允许我们提供一个初始累加器值。我们需要这样做,因为我们将在遍历字节码集合时跟踪很多状态,而双参数版本几乎要求累加器与列表中的项相似。(c.f.
(reduce+[1234])

在使用功能性折叠时,您需要考虑您正在积累的内容,以及输入集合中的每个元素如何对该积累做出贡献。如果查看Python代码,有三个值可以在每次循环中更新:

  • 输出语句(
    self.code
  • 标签映射(
    self.labels
  • 程序计数器(
    pc
循环期间不会写入任何其他内容。因此,我们的累加器值需要存储这三个值

前一位是最重要的部分

一旦你做到了这一点,剩下的就应该很容易了。我们需要一个初始累加器值,它没有代码,没有标签映射,以及一个从0开始的PC。在每次迭代中,我们将通过以下两种方式之一更新累加器:

  • 添加新的标签映射
  • 添加新的输出程序语句,并递增程序计数器
现在,输出:

[[[:load 0] [:load 1] [:add] [:store 0]] 
 {:entry 0}
 4]
这是一个三元素向量。第一个元素是程序。第二个元素是标签映射。第三个元素是下一个PC值。现在,您可以修改parse以只生成两个值;那不是一件不合理的事。您可能不想这样做有一些原因,但这更像是API设计的问题。我将把它作为练习留给读者

我还应该提到,最初,我省略了let块,只是内联了命名值。我决定把它们拉出来,希望能增加可读性。再说一次,我不知道哪个更地道。这可能更像是每个项目的约定


最后,我不知道monad是否真的在Clojure社区中流行起来,但是您也可以创建一个用于字节码解析的monad,并将操作“add statement”和“add label”定义为该monad中的值。这将大大增加设置的复杂性,但会简化实际的解析代码。事实上,它将允许您的解析代码看起来相当程序化,这可能是一件好事,也可能不是一件好事。(不用担心,它仍然是功能性的,没有副作用;monad只是让您隐藏管道。)如果您的Python示例非常代表您需要处理的数据类型,那么monad几乎肯定是不必要的开销。另一方面,如果你实际上需要管理比你的样本所指示的更多的状态,那么单子可能会帮助你保持理智

我将为您提供这类问题的一般解决方案

大多数循环都可以轻松地使用strait forward map、filter或reduce完成,如果您的数据结构是递归的,那么自然循环将是递归的

然而,您的循环是另一种循环。您的循环累积了一个结果——这建议使用reduce——但循环也携带了一个局部变量(pc),因此它不是一个简单的reduce

这是一种相当常见的循环。如果是,我会使用,但既然不是,我们将不得不将您的循环硬塞进reduce

让我们定义一个名为load的函数,它返回两个东西:已处理的代码和已处理的标签。我还将使用一个名为is label?的助手函数

此技术可用于将任何具有局部循环的累加器转换为reduce。这是完整的代码

(defn load [asm]
  (defn is-label? [x] (= (first x) :label))
  {:code (filter (complement is-label?) asm)

   :labels
   (first
    (reduce
     (fn [[result, pc] [_ arg :as inst]]
       (if (is-label? inst)
         [(assoc result arg pc) pc]
         [result (inc pc)]))
     [{} 0] asm))})

我还应该提到,我回答了,但是在这里被指示的。
  {:code (filter (complement is-label?) asm)

   :labels
   <<< CODE GOES HERE >>>
  }
   (first
    (reduce
     (fn [[result, pc] inst]

        << MORE CODE >>

     [{} 0] asm))
     (fn [[result, pc] [_ arg :as inst]]
       (if (is-label? inst)
         [(assoc result arg pc) pc]
         [result (inc pc)]))
(defn load [asm]
  (defn is-label? [x] (= (first x) :label))
  {:code (filter (complement is-label?) asm)

   :labels
   (first
    (reduce
     (fn [[result, pc] [_ arg :as inst]]
       (if (is-label? inst)
         [(assoc result arg pc) pc]
         [result (inc pc)]))
     [{} 0] asm))})