Browse code
Add the proof of concept implementation
fiddlerwoaroof authored on 26/10/2016 03:36:45
Showing 7 changed files
Showing 7 changed files
- README.md
- collection-classes.asd
- collections-for.lisp
- collections-sbcl-iterators.lisp
- collections.lisp
- example.lisp
- package.lisp
0 | 4 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
1 |
+;;;; collection-classes.asd |
|
2 |
+ |
|
3 |
+(asdf:defsystem #:collection-class |
|
4 |
+ :description "Defines a macro for declaring a class that is a collection of another class" |
|
5 |
+ :author "Edward Langley <el@elangley.org>" |
|
6 |
+ :license "MIT" |
|
7 |
+ :depends-on (#:alexandria |
|
8 |
+ #:for |
|
9 |
+ #:serapeum |
|
10 |
+ ) |
|
11 |
+ :serial t |
|
12 |
+ :components ((:file "package") |
|
13 |
+ (:file "collections") |
|
14 |
+ (:file "collections-for") |
|
15 |
+ #+sbcl |
|
16 |
+ (:file "collections-sbcl-iterators") |
|
17 |
+ )) |
|
18 |
+ |
0 | 19 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,22 @@ |
1 |
+(in-package :collection-class) |
|
2 |
+ |
|
3 |
+(defclass collection-iterator (for:iterator) |
|
4 |
+ ()) |
|
5 |
+ |
|
6 |
+(defmethod initialize-instance :after ((iterator collection-iterator) &key object) |
|
7 |
+ (setf (for:object iterator) |
|
8 |
+ (items object))) |
|
9 |
+ |
|
10 |
+(defmethod for:has-more ((iterator collection-iterator)) |
|
11 |
+ (not (null (for:object iterator)))) |
|
12 |
+ |
|
13 |
+(defmethod for:next ((iterator collection-iterator)) |
|
14 |
+ (let ((collection-items (for:object iterator))) |
|
15 |
+ (prog1 (car collection-items) |
|
16 |
+ (setf (for:object iterator) |
|
17 |
+ (cdr collection-items))))) |
|
18 |
+ |
|
19 |
+(defmethod for:make-iterator ((collection collection) &key) |
|
20 |
+ (make-instance 'collection-iterator :object collection)) |
|
21 |
+ |
|
22 |
+ |
0 | 23 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,28 @@ |
1 |
+(in-package :collection-class) |
|
2 |
+ |
|
3 |
+(defmethod sb-sequence:length ((sequence collection)) |
|
4 |
+ (length (items sequence))) |
|
5 |
+ |
|
6 |
+(defmethod sb-sequence:elt ((sequence collection) index) |
|
7 |
+ (elt (items sequence) index)) |
|
8 |
+ |
|
9 |
+(defmethod (setf sb-sequence:elt) (new-value (sequence collection) index) |
|
10 |
+ (setf (elt (items sequence) index) new-value)) |
|
11 |
+ |
|
12 |
+(defmethod sb-sequence:adjust-sequence ((sequence collection) length &key initial-element initial-contents) |
|
13 |
+ (let ((result (duplicate-collection sequence))) |
|
14 |
+ (when (or initial-element initial-contents) |
|
15 |
+ (setf (items result) |
|
16 |
+ (sb-sequence:adjust-sequence (items result) length |
|
17 |
+ :initial-element initial-element |
|
18 |
+ :initial-contents initial-contents))) |
|
19 |
+ result)) |
|
20 |
+ |
|
21 |
+(defmethod sb-sequence:make-sequence-like ((sequence collection) length &key initial-element initial-contents) |
|
22 |
+ (let ((result (duplicate-collection sequence))) |
|
23 |
+ (setf (items result) |
|
24 |
+ (sb-sequence:make-sequence-like (items result) length |
|
25 |
+ :initial-element initial-element |
|
26 |
+ :initial-contents initial-contents)) |
|
27 |
+ result)) |
|
28 |
+ |
0 | 29 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,61 @@ |
1 |
+(in-package :collection-class) |
|
2 |
+ |
|
3 |
+(defclass collection (standard-object #+sbcl sequence) |
|
4 |
+ ()) |
|
5 |
+ |
|
6 |
+(define-condition value-error () |
|
7 |
+ ((value :initarg :value :accessor value))) |
|
8 |
+ |
|
9 |
+(defgeneric push-item (item collection) |
|
10 |
+ (:documentation "Push item onto the beginning of the collection")) |
|
11 |
+ |
|
12 |
+(defgeneric items (collection) |
|
13 |
+ (:documentation "Get the items from a collection")) |
|
14 |
+ |
|
15 |
+(defgeneric duplicate-collection (collection)) |
|
16 |
+ |
|
17 |
+(defgeneric copy-instance (object &rest initargs &key &allow-other-keys) |
|
18 |
+ (:documentation "Makes and returns a shallow copy of OBJECT. |
|
19 |
+ |
|
20 |
+ An uninitialized object of the same class as OBJECT is allocated by |
|
21 |
+ calling ALLOCATE-INSTANCE. For all slots returned by |
|
22 |
+ CLASS-SLOTS, the returned object has the |
|
23 |
+ same slot values and slot-unbound status as OBJECT. |
|
24 |
+ |
|
25 |
+ REINITIALIZE-INSTANCE is called to update the copy with INITARGS.") |
|
26 |
+ (:method ((object standard-object) &rest initargs &key &allow-other-keys) |
|
27 |
+ (let* ((class (class-of object)) |
|
28 |
+ (copy (allocate-instance class))) |
|
29 |
+ (dolist (slot-name (mapcar #'closer-mop:slot-definition-name (closer-mop:class-slots class))) |
|
30 |
+ (when (slot-boundp object slot-name) |
|
31 |
+ (setf (slot-value copy slot-name) |
|
32 |
+ (slot-value object slot-name)))) |
|
33 |
+ (apply #'reinitialize-instance copy initargs)))) |
|
34 |
+ |
|
35 |
+ |
|
36 |
+; TODO: actually use item-class... |
|
37 |
+; TODO: finish initform handling. Have to figure out how to make initform work with push-item |
|
38 |
+(defmacro define-collection ((name item-class &key (initform '(list))) (&rest supers) &body ((&rest slots) &rest other-stuff)) |
|
39 |
+ (with-gensyms (item-slot-sym) |
|
40 |
+ `(progn (defclass ,name (,@supers collection) |
|
41 |
+ ((,item-slot-sym :initform ,initform :accessor items) |
|
42 |
+ ,@slots) |
|
43 |
+ ,@other-stuff) |
|
44 |
+ (defmethod duplicate-collection ((collection ,name)) |
|
45 |
+ (let ((result (copy-instance collection))) |
|
46 |
+ (setf (items result) |
|
47 |
+ (copy-seq (items result))) |
|
48 |
+ result)) |
|
49 |
+ (defmethod push-item ((item ,item-class) (collection ,name)) |
|
50 |
+ (push item (items collection)))))) |
|
51 |
+ |
|
52 |
+(defmethod random-item ((collection collection) &optional (random-state *random-state*)) |
|
53 |
+ (let* ((length (length (items collection))) |
|
54 |
+ (selected-index (random length random-state))) |
|
55 |
+ (elt (items collection) |
|
56 |
+ selected-index))) |
|
57 |
+ |
|
58 |
+(defmethod nth-item ((collection collection) (index integer)) |
|
59 |
+ (if (>= index 0) |
|
60 |
+ (elt (items collection) index) |
|
61 |
+ (error 'value-error :value index))) |
0 | 62 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,31 @@ |
1 |
+(defpackage #:collection-class.example |
|
2 |
+ (:use #:cl #:collection-class #:alexandria #:serapeum)) |
|
3 |
+ |
|
4 |
+(in-package :collection-class.example) |
|
5 |
+ |
|
6 |
+; Define the class to be contained in the collection |
|
7 |
+(defclass foo () |
|
8 |
+ ((%slot1 :initarg :slot1 :accessor slot1) |
|
9 |
+ (%slot2 :initarg :slot2 :reader slot2))) |
|
10 |
+ |
|
11 |
+; Define the collection: it's basically a class form, with the name being a list |
|
12 |
+; of (collection-name collected-class) |
|
13 |
+(define-collection (collected-foos foo) () |
|
14 |
+ ((%collection-slot :initarg :collection-slot :reader collection-slot))) |
|
15 |
+ |
|
16 |
+ |
|
17 |
+; Define a collection |
|
18 |
+(defparameter *collection* (make-instance 'collected-foos :collection-slot "howdy?")) |
|
19 |
+ |
|
20 |
+(push-item (make-instance 'foo :slot1 1 :slot2 2) *collection*) |
|
21 |
+(push-item (make-instance 'foo :slot1 2 :slot2 2) *collection*) |
|
22 |
+(push-item (make-instance 'foo :slot1 2 :slot2 3) *collection*) |
|
23 |
+ |
|
24 |
+; On sbcl, we implement the iterator protocol, so we can |
|
25 |
+; use the builtin sequence functions on the collection. |
|
26 |
+#+sbcl |
|
27 |
+(progn |
|
28 |
+ (collection-slot *collection*) #| --> "howdy?" |# |
|
29 |
+ (map 'list #'slot1 *collection*) #| --> (1 2 2) |# |
|
30 |
+ (map 'list #'slot2 *collection*) #| --> (2 2 3) |# |
|
31 |
+ ) |