Clojure “如何实施原始程序”;套用;
我已经越来越多地阅读了SICP和lisps/clojure,我发现自己在想apply实际上是如何实现的。当然也有一些愚蠢的方法,比如Clojure “如何实施原始程序”;套用;,clojure,scheme,lisp,sicp,Clojure,Scheme,Lisp,Sicp,我已经越来越多地阅读了SICP和lisps/clojure,我发现自己在想apply实际上是如何实现的。当然也有一些愚蠢的方法,比如(defn apply[f xs](eval(cons f xs))),但我找不到一个例子来说明真正的实现。我想,一旦我进入了SICP中的4.1,它将被涵盖,但失望地发现,他们根据已经存在的基础方案实现定义了apply 如何从一开始就实施这一点 编辑: 我想我问这个问题的方式有点不清楚。我知道如何根据SICP中提到的eval/apply交互实现apply。我指的是他
(defn apply[f xs](eval(cons f xs)))
,但我找不到一个例子来说明真正的实现。我想,一旦我进入了SICP中的4.1,它将被涵盖,但失望地发现,他们根据已经存在的基础方案实现定义了apply
如何从一开始就实施这一点
编辑:
我想我问这个问题的方式有点不清楚。我知道如何根据SICP中提到的eval/apply交互实现apply。我指的是他们在apply的元循环版本的定义中所依赖的底层apply-in方案。大体上如果您还没有用某种基本语言实现apply,那么如何使用参数列表调用函数,每个参数都单独传递。由于Clojure托管在JVM平台上(并且被设计为具有良好的Java互操作性),底层平台的特性非常突出 您可以在下面的apply on JVM源代码中看到: 请注意,出于效率考虑,对于最多4个数的算术,有一些特定的代码。 算术5及以上的处理效率较低
(defn apply
"Applies fn f to the argument list formed by prepending intervening arguments to args."
{:added "1.0"
:static true}
([^clojure.lang.IFn f args]
(. f (applyTo (seq args))))
([^clojure.lang.IFn f x args]
(. f (applyTo (list* x args))))
([^clojure.lang.IFn f x y args]
(. f (applyTo (list* x y args))))
([^clojure.lang.IFn f x y z args]
(. f (applyTo (list* x y z args))))
([^clojure.lang.IFn f a b c d & args]
(. f (applyTo (cons a (cons b (cons c (cons d (spread args)))))))))
ClojureScript实现也有相同的功能,但看起来与上面的JVM实现大不相同:
(defn apply
"Applies fn f to the argument list formed by prepending intervening arguments to args."
([f args]
(if (.-cljs$lang$applyTo f)
(let [fixed-arity (.-cljs$lang$maxFixedArity f)
bc (bounded-count (inc fixed-arity) args)]
(if (<= bc fixed-arity)
(apply-to f bc args)
(.cljs$lang$applyTo f args)))
(apply-to-simple f (seq args))))
([f x args]
(if (.-cljs$lang$applyTo f)
(let [arglist (list* x args)
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (inc (bounded-count fixed-arity args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f x (seq args))))
([f x y args]
(if (.-cljs$lang$applyTo f)
(let [arglist (list* x y args)
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (+ 2 (bounded-count (dec fixed-arity) args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f x y (seq args))))
([f x y z args]
(if (.-cljs$lang$applyTo f)
(let [arglist (list* x y z args)
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (+ 3 (bounded-count (- fixed-arity 2) args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f x y z (seq args))))
([f a b c d & args]
(if (.-cljs$lang$applyTo f)
(let [spread-args (spread args)
arglist (cons a (cons b (cons c (cons d spread-args))))
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (+ 4 (bounded-count (- fixed-arity 3) spread-args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f a b c d (spread args)))))
(定义适用)
“将fn f应用于参数列表,该列表是通过将插入的参数前置到args而形成的。”
([f args]
(如果(.-cljs$lang$applyTo f)
(let[固定算术(.-cljs$lang$maxFixedArity f)
bc(有界计数(包括固定算术)参数)]
(如果(我前一段时间做了,但我没有公开apply
。我确实提供了rest参数,因此,因为我有eval
和该语言中的宏,所以我多次尝试这样做。我很快发现宏是无用的,所以eval
是唯一的解决方案。您的示例有一个缺陷:
(defn mapply [f xs] (eval (cons f xs)))
(mapply cons '(1 (3)))
; ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn
原因是由eval
计算的结果表达式变为:
(cons 1 (3))
而不是
(cons '1 '(3))
因此,要模拟它,您需要确保已经计算的值不会再次得到计算。我们可以通过引用这些值来解决这一问题:
(defn m2apply [f xs] (eval (cons f (map #(list 'quote %) xs))))
(m2apply cons '(1 (3)))
; ==> (1 3)
是的..但是你真的做了比你需要的多得多的计算。对于一个有apply
的词法解释器,你只需要将它作为一个原语泄漏到环境中。是的,它是不引人注目的apply
的唯一目的是调用内部(原语)并在扩展环境中评估用户功能体。在一种尚未使用lisp的语言中,应用程序以及一整套原语和数据结构将在实现语言中实现,而它只会公开这些内容。我认为您无法在语言中从头定义它:在某一点上,您的语言age需要一种对一组参数实际调用函数的机制,而apply
正是这一点
这就是为什么它是一个原语:问你如何实现apply
就像问你如何实现cons
或+
:迟早事情需要彻底解决,你调用了一个没有在语言中定义的函数,或者只是在语言中部分定义的函数:+
,例如,可能部分是i在检查类型和从中提取实际机器编号方面实现,但迟早您会要求机器为您添加一些机器编号(或者,如果您的机器不支持直接添加,则可以执行一些等效操作).实现apply
的方式与实现函数调用的方式直接相关。如果您编译代码,您在运行时就有一个协议,您知道函数调用之间的值是如何交换的,apply
可以发出满足此协议的代码。我们也可以在一个快速而肮脏的解释器中这样做。Le让我们定义一个包:
(defpackage :interpreter (:use :cl))
(in-package :interpreter)
我们定义了一个函数对象,该对象具有可选名称、参数列表、代码以及一组正在关闭的绑定:
(defstruct fn name parameters code closed)
我们还定义了一个框架,它有一组绑定和一个可选的父框架:
(defstruct frame bindings parent)
这里我们有一个简单的解释器,我们将当前帧放在评估环境中:
(defstruct env frame)
绑定可以是FN类型的对象,也可以是cons对。我们编写泛型函数以使用统一API操作它们。函数和变量共享相同的命名空间:
(defgeneric name (object)
(:method ((fn fn)) (fn-name fn))
(:method ((pair cons)) (car pair)))
(defgeneric value (object)
(:method ((c cons)) (cdr c))
(:method ((fn fn)) fn))
我们定义了两个函数,my apply
和my eval
(declaim (ftype function my-apply my-eval))
有一个全球性的环境,这就是:
(defparameter *global-frame*
(make-frame
:bindings (list (make-fn :name '+
:parameters '(x y)
;; built-in
:code (lambda (x y) (+ x y)))
(make-fn :name 'addition
:parameters '(x y)
:code '(+ x y)))
:parent nil))
空环境隐式保留全局框架:
(defgeneric frame (env)
(:method ((empty null)) *global-frame*)
(:method ((env env)) (env-frame env)))
解析绑定涉及访问父帧:
(defun resolve (name frame &optional (on-error :error))
(labels ((recurse (frame)
(cond
(frame (or (find name (frame-bindings frame) :key #'name)
(recurse (frame-parent frame))))
((eql :error on-error) (error "Unknown: ~a" name)))))
(recurse frame)))
评估功能如下所示:
(defun my-eval (code env &aux (frame (frame env)))
(flet ((ev (exp) (my-eval exp env)))
(typecase code
(symbol (value (resolve code frame)))
(atom code)
(cons
(destructuring-bind (head . tail) code
(case head
(list (mapcar #'ev tail))
(let (destructuring-bind ((var val) expr) tail
(my-eval expr
(make-env :frame (make-frame :bindings `((,var . ,(ev val)))
:parent frame)))))
(thunk (make-fn :name nil
:parameters nil
:code (first tail)
:closed (frame-bindings frame)))
(apply (my-apply (ev (first tail))
(ev (second tail))
env))
(t (my-apply (resolve head (frame env))
(mapcar #'ev tail)
env))))))))
评估职能部门接受以下条款:
(list)
构建一个包含其参数计算结果的列表
(apply)
,计算所有参数并调用myapply
原语
(let())
,本地绑定
(thunk)
关闭当前环境,并生成不带参数的匿名闭包,该闭包返回
()
函数调用
- 符号解析为值,其他值按原样返回
内置的my apply
知道如何将参数动态绑定到值:
(defun my-apply (fn arguments env)
(assert (= (length arguments)
(length (fn-parameters fn)))
()
"Length mismatch when calling ~S with argsuments ~S"
fn
arguments)
(let ((code (fn-code fn)))
(typecase code
(function (apply code arguments))
(t (my-eval code
(make-env :frame
(make-frame :bindings (append (fn-closed fn)
(mapcar #'cons
(fn-parameters fn)
arguments))
:parent (frame env))))))))
例如:
(my-eval '(let (f (let (x 10) (thunk (addition x 5))))
(let (x 20) (apply f (list)))) nil)
=> 15
在上面的示例中,f
是一个函数,它关闭x
到10的绑定,并调用addition
。稍后进行的绑定不会被闭包看到。调用apply
解决f
的问题