2017-03-02 85 views
0

假設我們有兩個軟件包,每個軟件包定義一個類併爲相同名稱導出插槽/通用方法的符號。Common Lisp:CLOS和軟件包/如何導入和合並仿製藥

(defpackage pkg1 (:export _class1 _slot _reader _method)) 
(in-package pkg1) 
(defclass _class1() ((_slot :initform "SLOT111" :initarg :slot :reader _reader))) 
(defmethod _method ((self _class1)) (format t "SLOT-: ~a~%" (_reader self))) 

(defpackage pkg2 (:export _class2 _slot _reader _method)) 
(in-package pkg2) 
(defclass _class2() ((_slot :initform "SLOT222" :initarg :slot :reader _reader))) 
(defmethod _method ((self _class2)) (format t "SLOT=: ~a~%" (_reader self))) 

我們如何在第三個包中成功導入這些符號,成功合併(不是陰影)泛型?

(defpackage test) 
(in-package test) 
... ; here we somehow import symbols _slot, _reader and _method 
    ; from both packages, so they get merged (like in 'GNU Guile' or 'Gauche') 
(defvar v1 (make-instance '_class1)) 
(defvar v2 (make-instance '_class2)) 
(_reader v1) (_method v1) ; both must work 
(_reader v2) (_method v2) ; and these too 

回答

0

試圖通過MOP解決這個任務後,我想出了一個多simplier解決方法:

(defmacro wrapping-import 
      (sym-name &rest sym-list) 
    `(defmethod ,sym-name 
       (&rest args) 
    (loop for sym in '(,@sym-list) do 
      (let ((gf (symbol-function sym))) 
      (if (compute-applicable-methods gf args) 
       (return (apply gf args))))) 
    (error "No applicable method found in ~A" ',sym-name))) 

例子:

(defpackage p1 (:export say-type)) 
(in-package p1) 
(defmethod say-type ((v integer)) "int") 

(defpackage p2 (:export say-type)) 
(in-package p2) 
(defmethod say-type ((v string)) "str") 

(in-package cl-user) 
(wrapping-import say-type p1:say-type p2:say-type) 

(say-type "") ; -> "str" 
(say-type 1) ; -> "int" 

而且,這裏的原始解決方案:

(defmacro merging-import 
      (sym-name &rest sym-list) 
    (let ((gf-args (clos:generic-function-lambda-list 
        (symbol-function (first sym-list))))) 
    `(progn 
     (defgeneric ,sym-name ,gf-args) 
     (loop for sym in '(,@sym-list) do 
      (loop for meth 
        in (clos:generic-function-methods (symbol-function sym)) 
        do 
        (add-method #',sym-name 
           (make-instance 'clos:standard-method 
               :lambda-list (clos:method-lambda-list meth) 
               :specializers (clos:method-specializers meth) 
               :function  (clos:method-function  meth)))))))) 

請注意wrapping-import作品即使通用功能的簽名不匹配,而merging-import要求其lambda列表相等。
現在我想知道:爲什麼我們必須在2017年發明這樣的東西?爲什麼那些不在標準呢?

以防萬一有人需要它 - 一個宏,它在Python就像from pkg import *

(defmacro use-all-from 
      (&rest pkg-list) 
    `(loop for pkg-name in '(,@pkg-list) do 
     (do-external-symbols 
      (sym (find-package pkg-name)) 
      (shadowing-import (read-from-string (format nil "~a:~a" 
                 pkg-name sym)))))) 
2

對於CLOS,我真的是一個小菜鳥,所以我去年做了同樣的實驗。我的發現是CL沒有真正導出方法或合併方法。它導出可能具有綁定的符號。因此,你需要用符號的包,他們應該分享,也許放在那裏的文檔:

;; common symbols and documantation 
(defpackage interface (:export _slot _reader _method)) 
(in-package interface) 
(defgeneric _method (self) 
    (:documentation "This does this functionality")) 
(defgeneric _reader (self) 
    (:documentation "This does that functionality")) 

(defpackage pkg1 (:use :cl :interface) (:export _class1 _slot _reader _method)) 
(in-package pkg1) 
(defclass _class1() ((_slot :initform "SLOT111" :initarg :slot :reader _reader))) 
(defmethod _method ((self _class1)) (format t "SLOT-: ~a~%" (_reader self))) 

(defpackage pkg2 (:use :cl :interface) (:export _class2 _slot _reader _method)) 
(in-package pkg2) 
(defclass _class2() ((_slot :initform "SLOT222" :initarg :slot :reader _reader))) 
(defmethod _method ((self _class2)) (format t "SLOT=: ~a~%" (_reader self))) 

(defpackage test (:use :cl :pkg1 :pkg2)) 
(in-package test) 
(defvar v1 (make-instance '_class1)) 
(defvar v2 (make-instance '_class2)) 
(_reader v1) ; ==> "SLOT111" 
(_method v1) ; ==> nil (outputs "SLOT-: SLOT111") 
(_reader v2) ; ==> "SLOT222" 
(_method v2) ; ==> nil (outputs "SLOT-: SLOT222") 

您可以從測試看看發生了什麼事:

(describe '_method) 

_METHOD is the symbol _METHOD, lies in #<PACKAGE INTERFACE>, is accessible in 
4 packages INTERFACE, PKG1, PKG2, TEST, names a function. 
Documentation as a FUNCTION: 
This does this functionality 

#<PACKAGE INTERFACE> is the package named INTERFACE. 
It imports the external symbols of 1 package COMMON-LISP and 
exports 3 symbols to 2 packages PKG2, PKG1. 

#<STANDARD-GENERIC-FUNCTION _METHOD> is a generic function. 
Argument list: (INTERFACE::SELF) 
Methods: 
(_CLASS2) 
(_CLASS1) 

(describe '_reader) 

_READER is the symbol _READER, lies in #<PACKAGE INTERFACE>, is accessible in 
4 packages INTERFACE, PKG1, PKG2, TEST, names a function. 
Documentation as a FUNCTION: 
This does that functionality 

#<PACKAGE INTERFACE> is the package named INTERFACE. 
It imports the external symbols of 1 package COMMON-LISP and 
exports 3 symbols to 2 packages PKG2, PKG1. 

#<STANDARD-GENERIC-FUNCTION _READER> is a generic function. 
Argument list: (INTERFACE::SELF) 
Methods: 
(_CLASS2) 
(_CLASS1) 

這有副作用導入pkg1 _method將在pkg2實例上工作,如果您從使用pkg2的軟件包中獲得此類實例。

現在這裏有一頭大象。爲什麼不在interface中定義一個基類,並將其添加爲_class1_class2的父類。只需做一些改動就可以輕鬆完成,但這不是您要求的。

+2

定義的協議(/接口)這樣的意義,如果這些方法在概念上是不同類別相同的操作(作爲啓發式,如果一個文檔字符串可以有用地描述這兩種方法)。如果這些方法是完全不同的操作,那麼最好只使用包限定名稱,或者在方法名稱前加適當的(不同的)協議名稱。 – jkiiski

+0

謝謝,但即使此解決方案有效,從設計的角度來看也是完全錯誤的。如果兩個軟件包** _ pkg1 **和** _ pkg2 **由不同的開發人員編寫和維護,該怎麼辦?那麼爲了工作雙方都應該意識到對方?爲什麼** _ class1 **和** _ class2 **必須共享一些共同的祖先,如果它們完全不相關? – AlexDarkVoid

+0

@AlexDarkVoid我同意。如果我要設計一個lisp OOP系統,則導入將合併具有相同導入名稱的調度程序,如果它們的簽名是兼容的,並且連接將影響它們從中導入的庫,以便兩者都支持所有類型的導入庫以支持它們。除非你有一種編譯語言,否則它會變得混亂。 – Sylwester