这个Python代码的惯用Clojure等价物?
我用Python编写了一个简单的基于堆栈的虚拟机,现在我正试图用Clojure重写它,这很困难,因为我没有太多的Lisp经验。处理字节码,字节码表示为元组列表,如下所示:这个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]] 当一个函数对象加载字节码时,每
[("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
- 添加新的标签映射
- 添加新的输出程序语句,并递增程序计数器
[[[: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))})