Clojure 函数的方案(subst)
Scheme to Clojure Function (subst)
我正在阅读 Paul Graham 的 The Roots of Lisp
我已经尝试转换第 5 页上的函数 subst
,其定义如下:
(defun subst (x y z)
(cond ((atom z)
(cond ((eq z y) x)
('t z)))
('t (cons (subst x y (car z))
(subst x y (cdr z))))))
对应的Clojure
实现。我没有任何一种语言的生产经验(我一直在阅读 Clojure),所以任何帮助将不胜感激,因为我正在阅读这篇文章以了解 LISP 的根源。我最接近的是这个(但这是非常错误的):
(defn subst
[x y z]
(if (not (nil? z)) z x)
(if (= z y) x z)
(cons (subst x y (first z))
(subst (x y (rest z)))))
"Traduttore, traditore"
(这里可以译为"translator, traitor",但是这样就废了双关语,本身就很好玩)
很难在您的 Clojure 代码中暗示可能的修复,因为规范不明确:
如果你严格按照 Lisp 的根源,你将在 Clojure 之上实现一个 Lisp,subst
可能与书中的相似。
但是如果你想实现 Lisp 中常用的 subst
,这里显示的代码将无法实现。
尽管 Clojure 具有 cons
和 nil?
函数,但它们的含义与 Common Lisp 中的不同(分别为 cons
和 null
):参见 了解详情。
在翻译 subst
之前,您必须确定在 Clojure 中惯用的做法是什么。
通常subst
用于转换由cons单元组成的树;请注意,例如 subst
不会递归到向量、字符串等。在这些树中,树的特定子集是那些 Lisp 形式的树。事实上,subst
的一个重要用例是在代码生成期间搜索和替换表单。
据我所知,如果您将自己限制在 Clojure Cons
类型,您将不支持代码作为数据。
由于 Clojure 代码还使用向量和映射,您可能需要递归到此类对象中。所以,如何翻译subst
并不是一个容易指定的问题。
一个可能的起点是阅读 LispReader.java
以确定构成 AST 的对象集,并查看您想执行哪种代码遍历。
我的建议是先独立学习这些语言。有了一点经验,您将有更好的方法来了解它们之间的相似之处和不同之处。
这就是我要做的,包括一个单元测试来验证:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.walk :as walk]))
(defn subst
[replacement target listy]
(walk/postwalk (fn [elem]
(if (= elem target)
replacement
elem))
listy))
(dotest
(is= (subst :m :b [:a :b [:a :b :c] :d])
[:a :m [:a :m :c] :d]))
然而,我不会花很多时间阅读关于 Common Lisp 的 40 年前的文章,尽管我认为 Paul Graham 的书 Hackers & Painters 非常令人兴奋。
Clojure 已经将最先进的 lisp 发展了至少一个数量级(我想更像是 2 个数量级)。主要改进包括 JVM 的使用、持久数据结构、并发性、语法和数据文字,仅举几例。
请参阅此 Clojure learning resources 列表,并可能从 Getting Clojure 或类似内容开始。
更新
更多来自 Paul Graham 关于 Clojure
方案版本的翻译可能是这样的:
(defn my-subst [new old data]
(when-not (nil? data)
(cond (sequential? data) (cons (my-subst new old (first data))
(my-subst new old (next data)))
(= data old) new
:else data)))
user> (my-subst 1 :x '(1 2 :x (:x 10 :x [:x :z :x])))
;;=> (1 2 1 (1 10 1 (1 :z 1)))
这非常接近(尽管不完全相同,因为有不止一种原生集合类型,这让您面临选择:哪些应该被视为替换目标)。此示例处理 'listy'(顺序)结构,同时省略哈希映射和集合。
另一个问题是保留原始序列的类型和形式,这并不像听起来那么简单(例如 (into (empty (list 1 2 3)) (list 1 2 3))
=> (3 2 1)
所以你要做的,就是先决定替换的语义,而在scheme中只是一个自然的列表处理。
从已经提到的 clojure.walk
开始,使用它进行替换的最简单方法可能是
(defn subst [new old data]
(clojure.walk/prewalk-replace {old new} data))
user> (subst :z :x [1 :x 3 '(:x {:a :x}) #{:x 1}])
;;=> [1 :z 3 (:z {:a :z}) #{1 :z}]
我正在阅读 Paul Graham 的 The Roots of Lisp
我已经尝试转换第 5 页上的函数 subst
,其定义如下:
(defun subst (x y z)
(cond ((atom z)
(cond ((eq z y) x)
('t z)))
('t (cons (subst x y (car z))
(subst x y (cdr z))))))
对应的Clojure
实现。我没有任何一种语言的生产经验(我一直在阅读 Clojure),所以任何帮助将不胜感激,因为我正在阅读这篇文章以了解 LISP 的根源。我最接近的是这个(但这是非常错误的):
(defn subst
[x y z]
(if (not (nil? z)) z x)
(if (= z y) x z)
(cons (subst x y (first z))
(subst (x y (rest z)))))
"Traduttore, traditore"
(这里可以译为"translator, traitor",但是这样就废了双关语,本身就很好玩)
很难在您的 Clojure 代码中暗示可能的修复,因为规范不明确:
如果你严格按照 Lisp 的根源,你将在 Clojure 之上实现一个 Lisp,subst
可能与书中的相似。
但是如果你想实现 Lisp 中常用的 subst
,这里显示的代码将无法实现。
尽管 Clojure 具有 cons
和 nil?
函数,但它们的含义与 Common Lisp 中的不同(分别为 cons
和 null
):参见 subst
之前,您必须确定在 Clojure 中惯用的做法是什么。
通常subst
用于转换由cons单元组成的树;请注意,例如 subst
不会递归到向量、字符串等。在这些树中,树的特定子集是那些 Lisp 形式的树。事实上,subst
的一个重要用例是在代码生成期间搜索和替换表单。
据我所知,如果您将自己限制在 Clojure Cons
类型,您将不支持代码作为数据。
由于 Clojure 代码还使用向量和映射,您可能需要递归到此类对象中。所以,如何翻译subst
并不是一个容易指定的问题。
一个可能的起点是阅读 LispReader.java
以确定构成 AST 的对象集,并查看您想执行哪种代码遍历。
我的建议是先独立学习这些语言。有了一点经验,您将有更好的方法来了解它们之间的相似之处和不同之处。
这就是我要做的,包括一个单元测试来验证:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.walk :as walk]))
(defn subst
[replacement target listy]
(walk/postwalk (fn [elem]
(if (= elem target)
replacement
elem))
listy))
(dotest
(is= (subst :m :b [:a :b [:a :b :c] :d])
[:a :m [:a :m :c] :d]))
然而,我不会花很多时间阅读关于 Common Lisp 的 40 年前的文章,尽管我认为 Paul Graham 的书 Hackers & Painters 非常令人兴奋。
Clojure 已经将最先进的 lisp 发展了至少一个数量级(我想更像是 2 个数量级)。主要改进包括 JVM 的使用、持久数据结构、并发性、语法和数据文字,仅举几例。
请参阅此 Clojure learning resources 列表,并可能从 Getting Clojure 或类似内容开始。
更新
更多来自 Paul Graham 关于 Clojure
方案版本的翻译可能是这样的:
(defn my-subst [new old data]
(when-not (nil? data)
(cond (sequential? data) (cons (my-subst new old (first data))
(my-subst new old (next data)))
(= data old) new
:else data)))
user> (my-subst 1 :x '(1 2 :x (:x 10 :x [:x :z :x])))
;;=> (1 2 1 (1 10 1 (1 :z 1)))
这非常接近(尽管不完全相同,因为有不止一种原生集合类型,这让您面临选择:哪些应该被视为替换目标)。此示例处理 'listy'(顺序)结构,同时省略哈希映射和集合。
另一个问题是保留原始序列的类型和形式,这并不像听起来那么简单(例如 (into (empty (list 1 2 3)) (list 1 2 3))
=> (3 2 1)
所以你要做的,就是先决定替换的语义,而在scheme中只是一个自然的列表处理。
从已经提到的 clojure.walk
开始,使用它进行替换的最简单方法可能是
(defn subst [new old data]
(clojure.walk/prewalk-replace {old new} data))
user> (subst :z :x [1 :x 3 '(:x {:a :x}) #{:x 1}])
;;=> [1 :z 3 (:z {:a :z}) #{1 :z}]