我一直在阅读SICP并越来越多地进入lisps / clojure,我发现自己想知道如何实施申请。当然有一些愚蠢的方式,如(defn apply [f xs] (eval (cons f xs)))
,但我找不到一个例子来涵盖真正的实现。我想,一旦我在SICP中达到4.1,它就会被覆盖,但很遗憾地发现他们根据现有的基础方案实施来定义应用。
如何从头开始实施这一目标?
编辑:
我认为我问这个问题的方式有点不清楚。我知道如何在SICP中提到的eval / apply交互方面实现apply。我所指的是计划中的基本适用,它们在应用的metacircular版本的定义中落后。基本上......如果你没有用某种基本语言实现的应用,如何使用args列表调用一个函数,每个函数都单独传递。
答案 0 :(得分:2)
由于Clojure托管在JVM平台上(并且设计为具有出色的Java互操作性),底层平台的特性可以通过。
您可以在源代码中看到在JVM上应用:https://github.com/clojure/clojure/blob/clojure-1.9.0/src/clj/clojure/core.clj#L652
出于效率原因,请注意最多4个arities的特定代码。 5级及以上的Arities以较低效的方式处理。
(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)))))
答案 1 :(得分:1)
我前一段时间制作了dynamic lisp language而我没有曝光apply
。我确实提供了休息参数,因此我用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的语言中,apply和一整套原语和数据结构将在实现语言中实现,而它只是暴露出来。
答案 2 :(得分:1)
实现apply
的方式与实现函数调用的方式直接相关。如果编译代码,则在运行时有一个协议,您可以知道函数调用之间如何交换值,apply
可以发出满足此协议的代码。我们可以在一个快速而又脏的翻译中做同样的事情。我们来定义一个包:
(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 <fn-expr> <arg-expr>)
,评估所有参数并调用my-apply
原语。(let (<var> <val>) <expr>)
,本地绑定(thunk <expr>)
关闭当前环境并生成一个没有参数的匿名闭包,返回值<expr>
(<f> . <args>)
函数调用内置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
并构建一个空列表。对addition
的调用解析为(+ 10 5)
,它本身最终调用CL函数+。您可以(trace my-eval)
查看评估方式。上面的代码有点乱。
答案 3 :(得分:0)
我认为你不能在语言中从头开始定义它:在某些时候你的语言需要一种在一堆参数上实际调用函数的机制,apply
几乎就是这一点
这就是为什么它是一个原始的:询问你如何实现apply
就像问你如何实现cons
或+
:迟早需要触底来你调用一个函数它没有在语言中定义,或者只是部分地用语言定义:+
例如可以部分地通过检查类型和从中提取实际机器号来实现,但是迟早你会去要求机器为您添加一些机器编号(或者,如果您的机器不直接支持添加,则可以执行某些等效操作)。