有无将closure还原为lambda的API?

有无内置的将 closure 还原为 lambda 的 API? (需要能够递归处理 bindings 的)

如:

(let ((closure-to-lambda
       (lambda (x)
         `(let ,(mapcar
                 (lambda (sv)
                   (list (car sv) (cdr sv)))
                 (aref x 2))
            (lambda ,(aref x 0) ,@(aref x 1))))))
  (funcall
   closure-to-lambda
   (let ((a 1)) (lambda nil a))))
;; => (let ((a 1)) (lambda nil a)) 
#+name: 2025-07-30-19-34
#+begin_src emacs-lisp :eval yes :lexical t
(!let (ic2l)
 (!def ic2l
  (lambda (ic &optional new-env)
    (cond
     ;; do convertion only when `ic' is
     ;; an interpreted-closure.
     ((and (interpreted-function-p ic)
           (closurep ic))
      (let ((args (aref ic 0))
            (body (aref ic 1))
            (env (aref ic 2))
            (docstring
             (when (length> ic 4) (aref ic 4)))
            (iform
             (when (length> ic 5) (aref ic 5))))
        (when iform
          (setq body `(,iform ,@body)))
        (when docstring
          (setq body `(,docstring ,@body)))
        (if (equal '(t) env)
            `(lambda ,args ,@body)
          `(let ,(mapcar
                  (lambda (sv)
                    `(,(car sv)
                      ;; replace variable with new
                      ;; environment if required.
                      ,(or
                        (alist-get (car sv) new-env)
                        (ic2l (cdr sv) new-env))))
                  env)
             (lambda ,args ,@body)))))
     (t ic))))
 ic2l)
#+end_src
#+name: 2025-07-30-21-06
#+begin_src emacs-lisp :lexical t
(!def '!import
 (!let (!import (ic2l (make-symbol "ic2l")))
  (!def !import
   (lambda (path &optional bindings)
     ;; 除了 (car (last path)) 可能指向 symbol 或
     ;; closure 外,其他都应该指向 closure.
     (!let (env sym val first fi-prev-sym)
      (setq first (car path))
      (setq sym first)
      (setq path (cdr path))
      (setq val (symbol-function sym))
      (while (and path (closurep val)
                  (interpreted-function-p val))
        (setq env (aref val 2))
        (setq sym (car path))
        (setq path (cdr path))
        ;; 从 env 中,找到 symbol sym 所对应的
        ;; closure.
        (cond
         ((and
           (setq val (alist-get sym env))
           (cond
            ((and (closurep val)
                  (interpreted-function-p val))
             t)
            ;; function indirection
            ((symbolp val)
             (while (and val (symbolp val))
               (setq fi-prev-sym val)
               (setq val (symbol-function val)))
             (cond
              ((and (closurep val)
                    (interpreted-function-p val))
               t)
              ;; 这是一个特殊分支,只为内部使用:
              ;; (!def (!import '(!import ic2l)) ...)
              ((and (null val) (null path)
                    (eq first '!import))
               (setq val fi-prev-sym)))))))
         (t (setq val nil))))

      (when path (error "Not found %S" path))

      (if (or (null bindings) (symbolp val)) val
        ;; '((s1 v1)(s2 v2)) =>
        ;; '((s1 . v1)(s2 . v2))
        (setq env
              (mapcar
               (lambda (sv)
                 (cons (car sv) (cadr sv)))
               bindings))
        ;; eval lambda sexp with new environment.
        (eval (ic2l val env) env)))))

  !import))
#+end_src

!import 的使用场景:

#+begin_src emacs-lisp :lexical t :eval no
;; -*- lexical-binding: t; -*-
(!def 'package-A
 (!let (package-A log say-hi)
  (!def log
   (!let ((name "[A]"))
    (lambda (fmt &rest args)
      (apply
       #'message
       (concat name fmt) args))))
  (!def say-hi (lambda () (log "hi")))
  (!def package-A
   (lambda (&rest args)
     (ignore say-hi log)
     (!import `(package-A ,@args))))
  package-A))

(!def 'package-B
 (!let (package-B
        ;; reuse `log' function of
        ;; package-A, but change it's
        ;; name to ours.
        (log (!import '(package-A log)
                      '((name "[B]"))))
        say-hi)
  (!def say-hi (lambda () (log "hi")))
  (!def package-B
   (lambda (&rest args)
     (ignore say-hi)
     (!import `(package-B ,@args))))
  package-B))

(!let ((say-hi (package-A 'say-hi)))
 (say-hi)) ; => "[A]hi"

(!let ((say-hi (package-B 'say-hi)))
 (say-hi)) ; => "[B]hi"
#+end_src

一种将 lambda 的构造工作委派给其他函数的方法:

#+begin_src emacs-lisp :eval no
(defmacro !fn (&rest cdr)
  (declare (indent defun))
  (!let* ((body (cdr cdr))
          ;; skip docstring.
          (body (if (stringp (car body))
                    (cdr body)
                  body))
          ;; skip interactive form.
          (body (if (eq (car-safe (car body))
                        'interactive)
                    (cdr body)
                  body))
          decl gtor)
   (cond
    ((and
      ;; 试找 declare
      (when (eq (car-safe (car body))
                'declare)
        (setq decl (car body)))
      ;; 试找 declare 中的 '(!fn gtor)
      (catch 'break
        (mapcar
         (lambda (spec)
           (when (eq (car-safe spec) '!fn)
             ;; 让 gtor 决定如何生成 lambda
             (setq gtor (cadr spec))
             (when (and (symbolp gtor)
                        (not (functionp gtor)))
               (setq gtor (intern-soft
                           (format
                            "!fn-%S" gtor))))
             (when (functionp gtor)
               (throw 'break (gtor cdr)))))
         decl)
        nil)))
    (t `(lambda ,@cdr)))))
#+end_src

!fn 的使用场景

根据给定 Org链接 指向的代码块构造 lambda.

在这里,我们稍微对 Org代码块 做个区分。当前,我们将其划分为三类:A类 是 elisp 代码片段;B类 是 elisp lambda; C类 是非 elisp 语言的代码块。

举例说明:

A类:

#+name: 2025-08-03-20-38
#+begin_src emacs-lisp :var args='("a" "b") :results silent
(apply #'concat args)
#+end_src

B类:

#+name: 2025-08-03-20-39
#+begin_src emacs-lisp :var args='("a" "b") :results silent
(lambda (&rest args)
  (apply #'concat args))
#+end_src

C类:

#+name: 2025-08-03-20-40
#+begin_src python python :var args='("a" "b") :results silent
from functools import reduce
return reduce(lambda a,b:a+b, args)
#+end_src
#+name: 2025-08-03-20-41
#+begin_src C++ :var args='("a" "b") :includes '(<string> <iostream>)
std::string c = "";
for (int i = 0; i < sizeof(args)/sizeof(const char *); ++i)
  {
    c += std::string(args[i]);
  }
std::cout << c;
#+end_src

借助 !fn 和几个工具函数,我们可以实现从 Org代码块 加载 lambda:

(!let (s+ r)
 (!def s+
  (!fn (&rest args)
    (declare (!fn wrap-org-src-block))
    [[2025-08-03-20-38]]))
 (push (s+ "a" "b" "c") r)

 (!def s+
  (!fn (&rest args)
    (declare (!fn from-org-src-block))
    [[2025-08-03-20-39]]))
 (push (s+ "a" "b" "c") r)

 (!def s+
  (!fn (&rest args)
    (declare (!fn by-org-src-block))
    [[2025-08-03-20-40]]))
 (push (s+ "a" "b" "c") r)

 (!def s+
  (!fn (&rest args)
    (declare (!fn by-org-src-block))
    [[2025-08-03-20-41]]))
 (push (s+ "a" "b" "c") r)
 r)
;; => ("abc" "abc" "abc" "abc")

于是,我们可以很容易地从其他语言引入一些东西,比如,从 python 中引入矩阵乘法:

#+name: 2025-08-03-21-03
#+begin_src python :var a='((1) (2)) b='(3 4) :results silent
import numpy as np
a = np.mat(a)
b = np.mat(b)
c = a @ b
return c.tolist()
#+end_src
(!let ((m* (!fn (a b)
             (declare (!fn by-org-src-block))
             [[2025-08-03-21-03]])))
 (m*
  '((1 2 3)(4 5 6))
  '((7 8 9 10)(11 12 13 14)(15 16 17 18))))
;; => ((74 80 86 92) (173 188 203 218))

实现

针对 A, B, C类,我们分别用如下的三个工具函数生成 lambda:

A类:

#+name: 2025-08-03-20-33
#+begin_src emacs-lisp :eval no :lexical t
(!def '!fn-wrap-org-src-block
 (lambda (cdr)
   (unless (fboundp 'org-noweb-expand-link)
     (error
      "!fn-wrap-org-src-block: %s"
      "`org-noweb-expand-link' no found."))
   (read
    (format
     "(lambda %S %s)"
     (car cdr)
     (org-noweb-expand-link
      (format "%s" (car (last cdr))))))))
#+end_src

B类:

#+name: 2025-08-03-20-34
#+begin_src emacs-lisp :eval no :lexical t
(!def '!fn-from-org-src-block
 (lambda (cdr)
   (unless (fboundp 'org-noweb-expand-link)
     (error
      "!fn-from-org-src-block: %s"
      "`org-noweb-expand-link' no found."))
   (read
    (org-noweb-expand-link
     (format "%s" (car (last cdr)))))))
#+end_src

C类:

#+name: 2025-08-03-20-35
#+begin_src emacs-lisp :eval no :lexical t
(!def '!fn-by-org-src-block
 (lambda (cdr)
   (unless (fboundp 'org-exec)
     (error
      "!fn-by-org-src-block: %s"
      "`org-exec' no found."))
   `(lambda ,(car cdr)
      (org-exec
        ,(car (last cdr)) nil
        :eval "yes" :results "none"
        ,@(apply
           #'append
           (mapcar
            (lambda (a)
              (if (eq a '&rest) nil
                `(',a `(identity ',,a))))
            (car cdr)))))))
#+end_src
1 个赞