What is FOOP?
FOOP is the concept of functional object oriented programming in newLISP. It originated in a thread on the newLISP forum and was officially added to the interpreter for newLISP 9.3.
To be fair, the FOOP operator, :
, does not provide any truly new functionality to newLISP. However, it does make prototyping much more concise and expressive.
The basics
Prototyping is accomplished using a source context
(a lexical closure) and the new
keyword. Using the commonly contrived example of the Rectangle object:
(context 'Rectangle) (set 'x nil) (set 'y nil) (define (area) (* x y)) (context MAIN) (new Rectangle 'my-rectangle) (set 'my-rectangle:x 10) (set 'my-rectangle:y 20)
FOOP uses the convention of (object member member ...)
for storing object instances. Instance data, apart from the members, is encapsulated within the context, so all of the methods may access contextually set variables. Although not built into the interpreter, the functional convention,
(define (Class:Class) (cons (context) (args)))
…is typically used to create new contexts for FOOP. Using Class
, the above example would be written as:
(new Class 'Rectangle) (set 'my-rectangle (Rectangle 10 20))
Methods are attached via the normal syntax for declaring foreign context functions, but with the assumption of the argument being the (object member member ...)
convention and implicit indexing:
(define (Rectangle:area self) (* (self 1) (self 2)))
All of the normal operations that apply to contexts remain applicable for contexts created in this fashion. One interesting element of FOOP is that the methods and namespace are separate from the object’s properties. Therefore, an arbitrary list of values can be consed by a cloned context to create a new instance.
Constructors are easily overridden using default functors. In fact, Class:Class
merely automates the creation of a default functor for a new context.
So, if we wanted a Rectangle with default x and y values:
(define (Rectangle:Rectangle (x 10) (y 10)) (list (context) x y))
Solving problems with FOOP
Although FOOP would not be considered a true object oriented system by purists (who would call it prototyping), it is an excellent substitute for a true type system. For example, here is a simple interface for probing and validating command-line arguments:
;; In case Class is not yet defined, attempt to define (catch (define (Class:Class) (cons (context) (args)))) ;; Declare our classes (new Class 'Option) (new Class 'Switch) ; Option's identifying token (new Class 'Required) ; Defines option as required (new Class 'Predicate) ; Lambda to validate and option's value ;; Define a method to find this option in (main-args) (define (Option:get self , loc val) (set 'switch (last (assoc (self Switch)))) (if (set 'loc (find switch (main-args))) (cond ((and (starts-with switch "--") (catch (nth (+ 1 loc) (main-args)) 'val)) val) ((starts-with switch "--") true) ((starts-with switch "-") true) (true nil)) nil)) ;; Define a predicate to validate the value of our option. ;; In case the option does not come with a value, we skip the predicate ;; part and validate solely based on the value of Required. (define (Option:valid? self , val req pred req? pred?) (set 'val (:get self)) (set 'req? (assoc (self Required))) (set 'pred? (assoc (self Predicate))) (set 'req (if req? (true? val) true)) (set 'pred (if pred? ((last pred?) val) true)) (and req pred))
The Option class is made up of several other classes, Switch, Required, and Predicate. Switch is the token to identify the option in the command-line arguments. Switches that begin with — can take an optional value, such as --url https://artfulcode.net
. Required is another empty class that only needs to be a boolean value. Predicate optionally stores a lambda that will be used to validate the value found in (main-args)
.
Here is a sample of how we might use Option
:
(set 'url (Option (Switch "--url") (Required true) (Predicate (lambda (val) (true? (regex {^http://.*?$} val 17))))))
For a single on/off switch, we can do:
(set 'some-option (Option (Switch "-s") (Required nil)))
To get the values and validate them:
artful:~ newlisp test.lsp --url http://www.artfulcode.net -s (url:get) ; => "https://artfulcode.net" (some-option:get) ; => true (url:valid?) ; => true (some-option:valid?) ; => true
And for non-validating values:
artful:~ newlisp test.lsp --url "Hello world" (url:get) ; => "Hello world" (url:valid?) ; => nil
newLISP 9.3′s new association list processing functions make accessing member cells a snap – (assoc (object key))
. Obviously, this is more expressive and concise than a lot of recursive parsing of (main-args)
. The trade-off is the same as with any use of OOP: contexts are more expensive than list processing, especially when creating many small context instances.
This is obviously a contrived example and could be solved more efficiently quite easily, but it does a good job of illustrating FOOP.
What makes it functional?
That’s tough to quantify. However, if we want to shorten the class declarations in the code above to simplify our code, we could use use the higher order functions map and curry:
(map (curry new Class) '(Option Switch Required Predicate))
Other examples might be to compose custom classes as needed using macros, updating a list of class instances using map
, or currying class constructors to extend classes.
What next?
There are better and more extensive tutorials available in video form courtesy of Michael Michaels of neglOOk available on the newLisp website.