【问题标题】:Lisp-family: Different evaluation of a symbol call and symbol as argumentLisp-family:符号调用和符号作为参数的不同评估
【发布时间】:2015-12-15 09:28:40
【问题描述】:

在 lisp 系列(编辑:lisp-1)语言中是否有一种方法可以区分符号评估作为函数或参数的位置(即在评估时覆盖此符号的 eval) ?
作为一个例子(我不需要这个功能,这是一个例子),我想对一组对象实现某种中缀操作,可以由对象本身调用

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

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

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

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

【问题讨论】:

  • 谢谢,忘了说明,我说的是 lisp-1 语言。
  • 在我看来,您正在搜索的是一种打破 Referential transparency rule 的方法,这是编程中非常重要的原则(在函数式编程中更是如此),并且可以在此简化方式:“每件事在任何情况下都应该具有相同的含义”。这对程序的可读性、可证明性等有很大帮助。只是我的 2 美分。

标签: clojure scheme lisp racket s-expression


【解决方案1】:

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

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

  2. 您可以使用自己的函数覆盖默认的#%app,以重新定义应用程序,包括不属于structs 的内容。例如,您可以模拟 Clojure 的 (key map) 语法,因此 ('symbol dict) 实际上是 (hash-ref dict 'symbol)

【讨论】:

  • 覆盖 #%app 甚至可能是 #%datum(取决于 OP 的需要)绝对是要走的路。
  • clojure 有趣的将 hashmap 应用于 key,反之亦然,这正是促使我提出这个问题的原因。你碰巧知道它是如何在clojure中实现的吗?
  • 在 JVM 上的 Clojure 中,我认为它是在 Java 中处理的,而不是在 Clojure“本身”中处理的。 Clojure 值对应于实现IFn 及其invoke 方法的各种Java 类,这就是神奇之处。至少这是我对writing about looking at this a year ago 的回忆。
【解决方案2】:

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

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

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

并让它评估为 2。

【讨论】:

    【解决方案3】:

    我一直在尝试在类似 OO CLOS 的方案中使用默认过程的想法。例如。那个写法

    (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 语法来实现。

    【讨论】:

    • 看起来很整洁。您的实验对象系统是否在某处发布? :)
    • @artemonster 还没有。我仍在试图弄清楚如何从导入的模块中合并调度程序。
    【解决方案4】:

    TXR Lisp 方言中,问题是从另一端解决的。以 Lisp-2 方言为基础,我们是否可以为自己披上 Lisp-1 方言的一些表现力优势,比如从大量使用更高级别的程序中消除(function ...)#'funcall 噪音?排序函数?

    设计以一个名为dwim 的特殊运算符为中心,它代表“按我的意思做”或“以一种智能且有意义的方式调度”。

    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 括号放在上面。

    此外,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 宏及其派生的“表亲”。

    Range Extraction 来自 Rosetta Code 的任务:

    (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 方法的形式提供此功能。这在 Rosetta 中的累加器工厂任务的 "OOP-Based" solution 中得到了演示:

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

    我们可以实例化一个(new (accum 9)),它会在作为函数调用时产生值101112、...。可以为 1 以外的增量提供可选的 delta 参数:

    (let ((acc (new (accum 0))))
      (list [acc 5] [acc 5] [acc])) -> (5 10 11)
    

    【讨论】:

      猜你喜欢
      • 2018-01-29
      • 1970-01-01
      • 1970-01-01
      • 2021-08-06
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-17
      相关资源
      最近更新 更多