Clojure “如何实施原始程序”;套用;

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。我指的是他

我已经越来越多地阅读了SICP和lisps/clojure,我发现自己在想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
的问题