Lisp-family:符号调用和符号作为参数的不同评估

Lisp-family: Different evaluation of a symbol call and symbol as argument

在 lisp 家族(编辑:lisp-1)语言中有没有一种方法可以区分符号求值的位置是作为函数还是作为参数(即覆盖这个符号的求值,当它是评价)?
作为一个例子(我不需要这个功能,这是一个例子),我想在一组对象上实现某种中缀操作,它可以被一个对象本身调用

(my-obj some-operator arg1 ...)  

实际上会将函数 some-operator 应用于 my-obj 和参数。
但是当此对象在代码中的其他任何地方用作参数时,例如:

(some-function my-obj &args...) 

它将评估为 my-obj 的值。
谢谢。

作为 lisp-1 基本上意味着您对组合的第一个插槽的评估与任何其他插槽没有任何不同。无论如何,要使您编写的代码具有这种行为,您需要将其转换为按照 lisp-1 规则执行所需操作的代码。因此,您需要实现一个执行此转换的宏。

例如,如果你想要中缀运算符,你需要写一些宏 infix 然后也许你可以写:

(infix (+ - * /) (1 + 2 * 5 - 3) / 4)

并将其计算为 2。

我一直在研究在类似 OO CLOS 的 Scheme 中使用默认过程的想法。例如。那个写作

(obj 5 10)

如果 obj 是过程或方法,将验证 obj 并使用参数应用它,但如果不是,它将与默认调度程序相同,例如。

(default-dispatcher obj 5 10)

在这样的方案中,可以制作矢量访问器:

(define-method default-dispatcher 
  "Default dispatcher for vectors"
  ([obj %vector] [index %number])       -> (vector-ref obj index)
  ([obj %vector] [index %number] value) -> (vector-set! obj index value)
  (args ...)                            -> (error "No such method"))

; example usage
(define vec (vector 4 5 6 7))
[vec 1]     ; => 5
[vec 1 10] 
[vec 1]     ; => 10

在 Racket 中,这可以通过更改语言 #%app 语法来实现。

在 Racket 中,可以本着这种精神做几件事:

  1. 你可以定义一个struct并给它一个prop:procedure。当在应用程序中提供 struct 的实例时,将调用该过程。

  2. 您可以使用您自己的函数覆盖默认值 #%app,以重新定义应用程序,包括不属于 struct 的内容。例如,你可以做一些事情,比如模仿 Clojure 的 (key map) 语法,这样 ('symbol dict) 实际上就是 (hash-ref dict 'symbol).

TXR Lisp 方言中,问题是从另一端解决的。从 Lisp-2 方言作为基础开始,我们能否利用 Lisp-1 方言的一些表达优势来打扮自己,比如消除程序中的 (function ...)#'funcall 噪音广泛使用高阶函数?

该设计以一个名为 dwim 的特殊运算符为中心,它代表 "Do What I Mean" 或 "Dispatch, in a Way that is Intelligent and Meaningful"。

dwim 运算符的调用使用方括号进行修饰,称为 "DWIM Brackets"

dwim 运算符不仅仅是 Lisp-2 上的一个宏;它实际上改变了名称查找规则。当我们有

(dwim a b c (d e f) g)

或等价地:

[a b c (d e f) g]

所有符号形式的参数(abcg)都使用特殊规则解析,该规则将函数和变量命名空间。这是语言的核心。操作员可以直接访问环境以实现这一点。

特殊处理不会递归到 (d e f),这是一个普通的 Lisp-2 形式。如果你想要语义,你必须把 DWIM Brackets 放在上面。

此外,dwim 运算符可由宏扩展正确处理。例如,给定:

(symacrolet ((localfun whatever))
  (flet ((localfun () ...)))
    [a b c localfun]    ;; refers to the flet as a function object!
    (a b c localfun)))  ;; refers to the symbol macro!

宏扩展器知道 dwim 及其语义,因此它考虑了 localfun 引用函数和变量命名空间的可能性。任一命名空间中最接近的词法绑定是 flet,因此符号宏扩展被抑制(阴影)。

dwim 语义隐式地用于部分求值 op 宏及其派生的 "cousins"。

Range Extraction 来自 Rosetta 代码的任务:

(defun range-extract (numbers)
  `@{(mapcar [iff [callf > length (ret 2)]
                  (ret `@[@1 0]-@[@1 -1]`)
                  (ret `@{@1 ","}`)]
             (mapcar (op mapcar car)
                     (split [window-map 1 :reflect
                                        (op list @2 (- @2 @1))
                                        (sort (uniq numbers))]
                            (op where [chain second (op < 1)])))) ","}`)

Y Combinator:

;; The Y combinator:
(defun y (f)
  [(op @1 @1)
   (op f (op [@@1 @@1]))])

;; The Y-combinator-based factorial:
(defun fac (f)
  (do if (zerop @1)
         1
         (* @1 [f (- @1 1)])))

;; Test:
(format t "~s\n" [[y fac] 4])

此外,TXR Lisp 中可以调用各种有用的函数。例如,每个序列(列表、向量或字符串)都被视为将数字索引映射到元素的函数。因此我们可以这样做:

(mapcar "abc" #(2 1 0))  -> #(#\c #\b #\a)

接受的答案描述了一种将结构视为函数的 Racket 机制。 TXR 以 lambda 方法的形式具有此功能。这在 "OOP-Based" solution 到 Rosetta 中的 Accumulator Factory 任务中得到了演示:

(defstruct (accum count) nil
  (count 0))

(defmeth accum lambda (self : (delta 1))
  (inc self.count delta))

我们可以实例化一个 (new (accum 9)),它会在作为函数调用时产生值 101112、...。可以为 1:

以外的增量提供可选的增量参数
(let ((acc (new (accum 0))))
  (list [acc 5] [acc 5] [acc])) -> (5 10 11)