atlas-engineer / nclasses

A `define-class` macro for less boilerplate
Other
7 stars 2 forks source link

+TITLE: NClasses

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

** Examples

A basic session:

TODO: Finish me!

+begin_src lisp

(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)

+end_src

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=):

+begin_src lisp

(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)))

+end_src

** Helpers beyond =define-class= *** =define-generic=

=define-generic= is made to shorten the frequent pattern of generic with one method:

+begin_src lisp

(defgeneric foo (a b c) (:method ((a integer) (b symbol) c) (bar)) (:documentation "Some FOO documentation."))

+end_src

Such a scary bloated code often makes one to use the neat =defmethod= instead:

+begin_src lisp

(defmethod foo ((a integer) (b symbol) c) "Some FOO documentation." (bar))

+end_src

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=:

+begin_src lisp

(define-generic foo ((a integer) (b symbol) c) "Some FOO documentation." (bar))

+end_src

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:

+begin_src lisp

(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."))

+end_src

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:

+begin_src lisp

(define-generic foo ((a integer)) (bar a) (:export-generic-name-p t))

+end_src

** 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:

Both of these patterns can be used together, dramatically shortening the code:

+begin_src lisp

(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)))

+end_src

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!