6.21 Classes - naver/lispe GitHub Wiki
LispE Class System Documentation
The LispE class system provides a simple yet powerful way to create and manage objects. It integrates seamlessly with other language features like pattern matching and predicates.
1. Defining a Class ✏️
You define a class using the class@
macro. The definition includes the class name, its fields, and its methods.
Syntax
(class@ ClassName (field1 field2 ...)(defun method1 ...)(defun method2 ...))
Fields
Fields are the data members of the class. They are defined in a list following the class name.
-
Simple Fields: Declared as a simple atom (e.g.,
x
,y
). Their initial value is provided during instantiation. -
Fields with Default Values: Declared as a list containing the atom and its default value (e.g.,
(z 1)
). This value is used if one is not provided during instantiation.
Methods
Methods are functions bound to a class that operate on its instances. They are defined using defun
inside the class
block. Methods have direct access to the fields of their instance.
Example
; Defines a class named 'truc' with fields x, y, and z
(class@ truc (x y z)
(defun appel(u)
(setq x 100)
(println x y z)
(+ x y z u)
)
)
; Defines a class named 'machin' with fields x, y, and z (with a default value of 1).
(class@ machin (x y (z 1))
; Defines a method 'appel' that takes one argument 'u'.
(defun appel(u)
(println 'machin)(+ x y z u))
; Defines a method 'configure' that takes one argument 'e'.
(defun configure(e)
(setqi x e)
(setqi v 100))) ; add a new field: `v`
2. Creating an Instance (Instantiation) ✨
You create an instance of a class by calling the class name as if it were a function.
Syntax
(ClassName arg1 arg2 ...)
The arguments are mapped positionally to the fields defined in the class.
-
(setq r (truc 10 20 30))
creates an instance oftruc
, settingx
to 10,y
to 20, andz
to 30. -
(setq m (machin 10 12))
creates an instance ofmachin
, settingx
to 10,y
to 12, andz
to its default value of 1.
3. Working with Instances ⚙️
Once you have an instance, you can call its methods and access or modify its fields.
Calling Methods
To call a method, you use the instance variable as a function, passing the method call as an argument.
-
Single Call:
(r (appel 10))
calls theappel
method on ther
instance with the argument 10. -
Chained Calls: You can chain multiple method calls. The value of the last call in the chain is returned.
(m (configure 1000) (appel 20))
first callsconfigure
onm
and then callsappel
. The final result is the value returned by(appel 20)
.
Call with class definition
Note that in order to speed processing, you can also provide the class definition in the call: (instance class (call) (call)...)
.
This call is a bit more verbose, but it allows the interpreter to know in advance the type of the instance and the execution is then more efficient.
(r truc (appel 10)) ; we add the class definition in the call itself
@
Operator
Accessing Fields: The To access field values from outside the class, use the @
operator.
-
Access by Name:
(@ m 'x)
retrieves the value of fieldx
from instancem
. -
Access by Index:
(@ m 1)
retrieves the value of the second field (0-indexed), which isy
, from instancem
.
setq
vs. setqi
Modifying Fields: You can modify an instance's fields from within its methods.
-
setq
: This is the standard assignment operator. It will modify the value of an existing field locally in the function, but won't modify the field itself. -
setqi
: This is a special "instance" assignment operator with dual functionality.-
Modify: If the field exists,
setqi
modifies the existing fieldx
. -
Create: If the field does not exist,
setqi
dynamically adds the new field to the instance.(setqi v 100)
creates a new fieldv
on the instance and sets its value to 100.
-
-
Access by Name:
(set@ m 'x 1000)
modifies the value of fieldx
from instancem
. -
Access by Index:
(set@ m 1 23)
modifies the value of the second field (0-indexed), which isy
, from instancem
.
4. Derivation
You can derive a class from another class with the operator: from@
. from@
replaces the argument definition of your class. When you derive from another class, the arguments are copied into the new class. You can then add other arguments after:
(class@ mother (x y)
(defun displaying()
(println x y)
)
(defun test(a)
(if (eq a x) y x)))
(class@ daughter (u) (from@ mother) ; we have now: u x y as arguments
(defun test(a) ; this function replaces the mother function...
(if (eq a x) u x))
(defun call_mother(a)
(from@ mother (test a)))
; note that, if the class doesn't add arguments:
(class@ daughterbis (from@ mother) ; we have now: x y as arguments
(defun test(a) ; this function replaces the mother function...
(if (eq a x) (+ x y) x))
(defun call_mother(a)
(from@ mother (test a)))
Note that the from@
can also be used to call the mother function of a derived class.
5. Constructor and destructor
It is possible to define constructor and destructor functions. However, contrary to most languages, there is no specific name for these functions. The constructor is simply a function that can be called when creating an instance. While the destructor is a function, which is defined with (toclean 'destructor)
.
(@class test(x init)
(defun config()
; clean is now the function that will be called when the instance is deleted
(toclean 'clean)
100
)
(defun clean()
(println 'cleaning)
)
)
(test 10 (config)) ; the constructor is an inner method in test called as an argument. The result of config is stored in: init
; At the end of the process:
; x is 10
; init is 100
Any functions in the class can be used either as a constructor or as a destructor.
6. Advanced Integration 🧩
LispE classes are first-class citizens and work with other parts of the language.
defpat
Pattern Matching with You can deconstruct class instances using defpat
. This allows you to write functions that react differently based on the class of their argument.
; Defines a pattern for 'teste' that matches instances of the 'truc' class,
; binding its fields to local variables x, y, and z.
(defpat teste([truc x y z])
(println 'TRUC x y z))
; Provides a different behavior for instances of 'machin'.
(defpat teste([machin x y z u])
(println 'MACHIN x y z u))
Usage in Control Flow
Method calls can be used as conditions in if
statements or within predicates defined with defpred
. A non-nil
return value is treated as true.
-
Predicate:
(defpred pred (x) (m (appel x)) ...)
defines a predicatepred
that succeeds if the(m (appel x))
call returns a true value. -
Conditional:
(if (r (appel 10)) (println 'ok) ...)
executes the first branch because the result of theappel
method is a number (160), which is treated as true.