【问题标题】:Generalizing Lisp functions泛化 Lisp 函数
【发布时间】:2017-07-27 22:53:28
【问题描述】:

出于快速原型设计的目的,我想开始构建 Common Lisp 中提供的一些基本函数的通用版本的库,而不是仅仅为手头的任务收集(或导入)特殊用途的函数。例如,map 函数适度泛化以对任何类型的序列进行操作,但不处理可调整向量。编写下面的特殊用途扩展似乎足以满足当前使用,但仍然有限:

(defun map-adjustable-vector (function adjustable-vector)
  "Provides mapping across items in an adjustable vector."
  (let ((new-adj-vec 
          (make-array (array-total-size adjustable-vector)
                        :element-type (array-element-type adjustable-vector)
                        :adjustable t
                        :fill-pointer (fill-pointer adjustable-vector))))
    (dotimes (i (array-total-size adjustable-vector))
      (setf (aref new-adj-vec i) (funcall function (aref adjustable-vector i))))
    new-adj-vec))

我想看看如何编写这样的函数,该函数还需要一个输出类型规范(包括一个新的“可调整向量类型”)并允许多种列表和向量作为输入——在​​其他单词,仿照map

更广泛地说,了解是否有与编写此类通用函数相关的基本原则或想法会很有用。例如,专门针对输出类型规范的通用方法是否是上述功能的可行方法?或者,也许,可以利用map 本身在泛化中的形象,如Mapping into a tree of nested sequences 上一篇文章中所述的核心转储?我不知道泛化是否有任何限制(除了一定的低效率),但我想看看它能走多远。

【问题讨论】:

  • map 可以很好地处理可调向量。
  • @sds,map 的结果向量的可调整性未定义。
  • 由于adjust-array 适用于不可调整的数组,我认为差异无关紧要。
  • @sds,除了如果数组是可调的,adjust-array 将返回相同的数组。
  • 是的,关键是map 破坏了填充指针。但我仍然对如何泛化内置函数感兴趣。 (我一直在考虑的另一个类似示例是coerce,它似乎可以扩展到涵盖其他合理的“等效”类型化对象转换。)

标签: mapping common-lisp generalization


【解决方案1】:

在这种情况下,map 可以被认为是使用make-sequence 来创建结果序列。

但是,您的问题过于笼统,任何答案基本上都是一种意见。你可以做任何你想做的事,但本质上,最直接的方法是扩展序列类型参数以包含具有填充指针和/或可调整的向量:

;;; For use by the following deftype only
(defun my-vector-typep (obj &key (adjustable '*) (fill-pointer '*))
  (and (or (eql adjustable '*)
           ;; (xor adjustable (adjustable-array-p obj))
           (if adjustable
               (adjustable-array-p obj)
               (not (adjustable-array-p obj))))
       (or (eql fill-pointer '*)
           (if (null fill-pointer)
               (not (array-has-fill-pointer-p obj))
               (and (array-has-fill-pointer-p obj)
                    (or (eql fill-pointer t)
                        (eql fill-pointer (fill-pointer obj))))))))

;;; A type whose purpose is to describe vectors
;;; with fill pointers and/or that are adjustable.
;;; 
;;; element-type and size are the same as in the type vector
;;; adjustable is a generalized boolean, or *
;;; fill-pointer is a valid fill pointer, or t or nil, or *
(deftype my-vector (&key element-type size adjustable fill-pointer)
  (if (and (eql adjustable '*) (eql fill-pointer '*))
      `(vector element-type size)
      ;; TODO: memoize combinations of adjustable = true/false/* and fill-pointer = t/nil/*
      (let ((function-name (gensym (symbol-name 'my-vector-p))))
        (setf (symbol-function function-name)
              #'(lambda (obj)
                  (my-vector-p obj :adjustable adjustable :fill-pointer fill-pointer)))
        `(and (vector ,element-type ,size)
              (satisfies ,function-name)))))

(defun my-make-sequence (resulting-sequence-type size &key initial-element)
  (when (eql resulting-sequence-type 'my-vector)
    (setf resulting-sequence-type '(my-vector)))
  (if (and (consp resulting-sequence-type)
           (eql (first resulting-sequence-type) 'my-vector))
      (destructuring-bind (&key (element-type '*) (type-size '* :size) (adjustable '*) (fill-pointer '*))
          (rest resulting-sequence-type)
        (assert (or (eql type-size '*) (<= size type-size)))
        (make-array (if (eql type-size '*) size type-size)
                    :element-type (if (eql element-type '*) t element-type)
                    :initial-element initial-element
                    :adjustable (if (eql adjustable '*) nil adjustable)
                    :fill-pointer (if (eql fill-pointer '*) nil fill-pointer)))
      (make-sequence resulting-sequence-type size
                     :initial-element initial-element)))

;; > (my-make-sequence '(my-vector :adjustable t) 10)

(defun my-map (resulting-sequence-type function &rest sequences)
  (apply #'map-into
         (my-make-sequence resulting-sequence-type
                           (if (null sequences)
                               0
                               (reduce #'min (rest sequences)
                                       :key #'length
                                       :initial-value (length (first sequence))))
         function
         sequences)))

;; > (my-map '(my-vector :adjustable t) #'+ '(1 2 3) '(3 2 1))

还有许多其他选择,例如简单地显式提供目标序列 (map-into)、提供具有特定参数的序列工厂函数或使用可特殊化的泛型函数(如果可能)、使用决定如何存储的包装器对象内部元素(例如,如果已排序,则可能使用树;如果元素很少,则可能使用 conses),并且可以返回任何类型的序列。

真的,这是一个开放式问题,主要取决于您的需求和/或想象力。

【讨论】:

  • 我想研究这个,谢谢。同时,您知道如何获取map 函数的源代码,例如在SBCL 上吗? function-lambda-expresssion 只返回 nil
  • SBCL 是开源的,它的代码在这里:https://github.com/sbcl/sbcl.
  • 欣赏 SBCL map 函数参考,但看起来代码非常依赖序列结构,因此在这方面可能没有太大的灵活性。
  • 我想我理解你的基本方法,它似乎涉及代码中嵌入的sequence 概念的大量返工/扩展。尽管这对于每个特定的扩展肯定会很好,但它让我想知道是否没有更“通用”的方式来扩展内置函数。似乎有人可能想要“映射”到点列表或许多其他类型的容器上。
  • 可以为每个特化调整泛型函数,比如定义为map+ 的功能扩展,它使用map 用于所有正常的序列相关参数特化,但调用其他map+ 泛型替代参数的方法?您是否知道添加此类通用函数功能的库,或者考虑到底层 CLOS 架构,这是不可行的?
猜你喜欢
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多