Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
#+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 - =hu.dwim.defclass-star= has a symbol export bug which cannot be fixed upstream, see hu-dwim#7 and hu-dwim#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: # 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 'class :width width :height height)= :: repetitive arguments. - =(apply #'make-instance 'class :key val :key2 val2 (when something (list :key3 val3)))= :: appending args to the =make-instance= form via =apply=. =make-instance*= abstracts these two patterns with shortcut arguments and apply forms respectively: - Shortcut arguments are a list of symbols that will be expanded into a list of eponymous keywords and args: #+begin_src lisp (make-instance* 'class (height width) :depth 3) ;; => ;; (make-instance 'class :height height :width width :depth 3) #+end_src - Apply form allows passing the last =apply= argument without explicitly calling =apply=: #+begin_src lisp (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))) #+end_src 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= - Renamed =defclass*= to =define-class= (although =defclass*= is still available as alias, alongside =define-class*=). - Renamed =defcondition*= to =define-condition*= (=defcondition*= is still available as alias of =define-condition*=). - Added convenience macros beyond class definition: - =define-generic= for concise generic function definition (with =defgeneric*= and =define-generic*= aliases). - =make-instance*= (with =make*= alias) to abstract eponymous keywords and arguments and inline the =apply #'make-instance= idiom. - Default slot value when initform is omitted is =nil=. To leave slot unbound, specify =:unbound= as initform value. - Only the core system has been kept, the ContextL, hu.dwim.def and Swank optional features have been removed. - New predicate name transformers =always-dashed-predicate-name-transformer= and =question-mark-predicate-name-transformer=. - New type inference options: =:automatic-types-p= and =:type-inference=. - Default accessor transformer now follows the slot name. hu.dwim.defclass-star default accessor is available as =dwim-accessor-name-transformer=. - Bug fixes: - No longer try to export =NIL=. - Always return the class. - Avoid unneeded =progn=. - Do not generate generic functions and accessors in foreign packages when =:accessor-name-package= is =:slot-name= and =:accessor= is not provided. (If accessor already exists in foreign package, then the new one is generated.) ** Change Log *** 0.6.1 - Remove =NASDF= as a dependency. *** 0.6.0 - Make =define-generic= declaration parsing smarter. - Ensure more correct =define-generic= body parsing. - Interpret a single-string body as method body and signal warnings due to the ambiguity of it. *** 0.5.0 - Auto-generate documentation for class predicates. - Auto-generate documentation for slot accessors. - Add =:export-generic-name-p= option to =define-generic=. *** 0.4.0 - Add =make-instance*= and =define-generic= convenience macros. - Add alias macros, like =defclass*=, =defcondition*=, =defgeneric*=, and =make*=. - Ensure documentation is always set for classes, generics, and conditions. *** 0.3.0 - Default to nil when slot value is unspecified. - Enable accessor generation in foreign package when it already exists. - Bug fixes. *** 0.2.1 - Fix =default-accessor-name-transformer= to follow =:accessor-name-package=. - Do not generate accessors in foreign packages when =:accessor-name-package= is =:slot-name= and =:accessor= is not provided. *** 0.2.0 - Fix =export-predicate-name-p= class option. - Allow type inference to check for types in superclasses. ** 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!