NClasses provides helper macros to help write classes and conditions with less boilerplate.
It's a fork of [[https://github.com/hu-dwim/hu.dwim.defclass-star][hu.dwim.defclass-star]].
** Motivation
=hu.dwim.defclass-star= has a symbol export bug which cannot be fixed upstream, see https://github.com/hu-dwim/hu.dwim.defclass-star/pull/7 and https://github.com/hu-dwim/hu.dwim.defclass-star/issues/12 for a discussion.
The macro and package names of hu.dwim.defclass-star prove to be rather unwieldy. Emacs can automatically highlight =define-class= as a macro, but not =defclass*=.
This library offers new features that wouldn't be accepted upstream, like [[https://github.com/hu-dwim/hu.dwim.defclass-star/pull/3][type inference]].
This library goes beyond class definitions in providing more utility macros e.g. =define-generic= and =make-instance*=.
** Examples
A basic session:
(define-class foo () ((slot1 :initarg nil) (slot2 \"hello!\") (unexported-slot :export nil)) (:export-class-name-p t) (:export-accessor-names-p t) (:accessor-name-transformer #'nclasses:default-accessor-name-transformer))
(make-instance 'foo :my-slot1 17)
See the [[file:source/package.lisp][package]] documentation for a usage guide and more examples.
** Default class options
If you want to change the default class options, say, for a package, you can simply define a wrapping macro (without importing =nclasses:define-start=):
(defmacro define-class (name supers slots &rest options)
"nclasses:define-star' with automatic types and always-dashed predicates."
(nclasses:define-class ,name ,supers ,slots
,@(append
'((:automatic-types-p t)
(:predicate-name-transformer 'nclasses:always-dashed-predicate-name-transformer))
options)))
** Helpers beyond =define-class= *** =define-generic=
=define-generic= is made to shorten the frequent pattern of generic with one method:
(defgeneric foo (a b c) (:method ((a integer) (b symbol) c) (bar)) (:documentation "Some FOO documentation."))
Such a scary bloated code often makes one to use the neat =defmethod= instead:
(defmethod foo ((a integer) (b symbol) c) "Some FOO documentation." (bar))
While convenient and short, standalone method definition auto-generates a generic function that's neither documented nor inspectable. =define-generic= solves this problem by making =defgeneric= form shorter and more =defmethod=-like, without any loss of semantics. The previous form looks like this with =define-generic=:
(define-generic foo ((a integer) (b symbol) c) "Some FOO documentation." (bar))
This form expand to exactly the same generic definition as the one above, while being as concise as the defmethod version.
The body or =define-generic= is automatically wrapped into a =:method= option, so there could be several body forms. If any of these body forms is a =defgeneric= option, it's safely put as defgeneric option outside the implied method:
(define-generic foo ((a integer) (b symbol) c) "Some FOO documentation." ; Docstring should always go first. (:method-combination progn) (bar) (:generic-function-class foo-class)) ;; => ;; (defgeneric foo (a b c) ;; (:method ((a integer) (b symbol) c) ;; (bar)) ;; (:method-combination progn) ;; (:generic-function-class foo-class) ;; (:documentation "Some FOO documentation."))
See the =define-generic= documentation for more examples and details.
*** :export-generic-name-p (option) and *export-generic-name-p\ (variable) These allow to export generic name after defining it:
(define-generic foo ((a integer)) (bar a) (:export-generic-name-p t))
** make-instance There are several idioms that heavily object-oriented CL code converges to:
=make-instance*= abstracts these two patterns with shortcut arguments and apply forms respectively:
(make-instance* 'class (height width) :depth 3) ;; => ;; (make-instance 'class :height height :width width :depth 3)
(make-instance* 'class :width 3 :height 5 (when three-dimentions (list :depth 3))) ;; => ;; (apply #'make-instance 'class :width 3 :height 5 (when three-dimentions (list :depth 3)))
Both of these patterns can be used together, dramatically shortening the code:
(make-instance* 'class (width height) (when three-dimentions (list :depth 3))) ;; => ;; (apply #'make-instance 'class :width width :height height (when three-dimentions (list :depth 3)))
Note that using either of these conveniences as the sole =make-instance*= argument is an ambiguous case that should be avoided by providing either shortcuts or apply form as an explicit NIL/().
See the =make-instance*= documentation for more examples and details.
** Changes from =defclass-star=
** Change Log
*** 0.6.1
*** 0.6.0
*** 0.5.0
*** 0.4.0
*** 0.3.0
*** 0.2.1
*** 0.2.0
** Alternatives
=defclass/std= is another popular library with a similar goal, but with more insistance on conciseness, maybe at the expanse of readability. In particular, it implements a way to specify slots by properties which may seem unnatural (we read slots by their name, not by their properties).
** Implementation notes
Metaclasses would not be very useful here since most of our features need to be enacted at compile-time, while metaclasses are mostly useful on classe /instances/.
** History
NClasses was originally developed for [[https://nyxt.atlas.engineer][Nyxt]], so the "N" may stand for it, or "New", or whatever poetic meaning you may find behind it!