LFE Flavors package
Implements the Lisp Machine flavors system in/and for LFE.
This is becoming less and less experimental. There are older experimental versions which test different implementation methods.
NOTE: in these descriptions we will not describe the Lisp Machine flavors. Check here for the Lisp Machine manual, our stuff is in chapter 21.
In this version a flavor instance is modelled with a process which is more LFEy. The instance variables are kept internally in a map. This means that a flavor instance more like a global object.
Note that sending messages is a synchronous operation. Explicitly sending a message to yourself is specially handled so it should not block but sending messages which result in circular calls will block. This is a general problem of synchronous messages communication and it still remains.
This model generates 2 modules for each flavor:
flav-flavor-core which contains functions describing all the properties of the flavor flav. This module is built at compile time.
flav-flavor which contains the access functions for which are called when sending messages to an instance of this flavor. It is built when the first instance of this flavor is made using
The reason for having 2 modules per flavor is that the access function module will only be built for flavors which actually have instances of them, so they won't be built for mixins. It also makes it easier to modify flavors that are being used.
NOTE: no extra .lfe files are actually created. A flav-flavor-core.beam is created directly from the original file containing the flavor definition but the flav-flavor module is compiled and directly loaded into the system without being written into a file. This means that all the flavor-core modules are generated at compile time and put in the ebin directory when the application is built.
To access the macros do
(include-file "include/flavors.lfe") or
(include-lib "flavors/include/flavors.lfe") if you have the
flavors application in your search path.
The following macros are available for defining flavors and methods:
(defflavor <flavor-name> (<var1> <var2> ...) (<flav1> <flav2> ...) <opt1> <opt2> ...) (defmethod (<operation>) <lambda-list> <form1> <form2>) (defmethod (<operation> <method-type>) <lambda-list> <form1> <form2>) (endflavor <flavor-name>) ;Must be last after the methods
Alternatively you can use compile time macros without including a macro definition file:
(flavors:defflavor <flavor-name> (<var1> <var2> ...) (<flav1> <flav2> ...) <opt1> <opt2> ...) (flavors:defmethod (<operation>) <lambda-list> <form1> <form2>) (flavors:defmethod (<operation> <method-type>) <lambda-list> <form1> <form2>) (flavors:endflavor <flavor-name>) ;Must be last after the methods
Note that the flavor name is no longer given when defining a method as
the method must be defined between the
defflavor and the
endflavor, they cannot be spread out in the file and
mixed. Also note the order of the method name (operation) and the
method type in the method definition.
The currently supported the options:
required-methods(methods given as
and the standard method types
after for the
init/1 method is also supported and it is automatically called
together with its daemons with the
init-plist as its argument when
an instance is created. There is a predefined method
which terminates the instance after calling the method and its before
and after daemons. The default methods for
terminate/0 do nothing and it is intended for those flavors which
need to use the methods will add daemons rather than redefining them.
For using the flavor definitions and accessing the instances there is:
(make-instance <flavor-name> <opt1> <value1> <opt2> <value2> ... ) (flavors:instantiate-flavor <flavor-name> <init-plist>) (send <object> <operation> <arg1> ...)
Alternatively with compile time macros:
(flavors:make-instance <flavor-name> <opt1> <value1> <opt2> <value2> ... ) (flavors:instantiate-flavor <flavor-name> <init-plist>) (flavors:send <object> <operation> <arg1> ...)
self is automatically bound to the actual instance so
it can be passed around. A primary method must now only return the
actual return value which will be returned from sending the
method. The return value of a
after daemon is
ignored. See the example flavors
To access the instance variables there are two predefined function
(get 'foo) will return the value
of the variable
(set 'bar 42) sets the value of the
When defining a flavor the component sequence is built as it should be
after daemons. There are rudimentary
property-list-mixin flavors included in the
You can now define many flavors in an LFE file together with other LFE
modules. Compiling the flavor definition file results in the
flav-flavor-core.beam file being generated. Local functions used by
the flavor methods can be defined in the same file but they MUST
come after the
defflavor definition and before the
From the LFE repl do
(c "f1" '(to-expand return)) to see the
resultant code generated by the
examples directory there is a
shapes sub-directory containing a number of shapes which have the
shapes flavor as a component. These are defined in separate files or in the file
multi.lfe which contains them all. Compiling the file compiles all the shape flavors:
> (c "examples/shapes/multi") (#(module circle-flavor-core) #(module rectangle-flavor-core) #(module shape-flavor-core))
There is also an LFE script
run-shapes.lfesh which can be run from the LFE repl. It creates some instances of the shapes
circle and sends them some messages:
> (run "examples/shapes/run-shapes.lfesh") Drawing rectangle at (10 20), width 5, height 6 Drawing rectangle at (110 120), width 5, height 6 Drawing circle at (15 25), radius 8 Drawing circle at (115 125), radius 8 Drawing rectangle at (0 0), width 30, height 15 #(flavor-instance rectangle rectangle-flavor <0.82.0>)
test sub-directory contains more flavors to test. For example
f2 is a component of
and the flavors
foo and its components
NOTE: Using the flavors now requires the latest version of the compiler which can handle the module not being the same as the file name. However the .beam still has the same name as the module as it must.
This is the original version in which the flavor instance is just a map which behaves just like any normal map or other data structure. This means that an instance is not a global object and when one is updated then a new one is created and the old one is there unchanged.
This means that this flavors version is like super records/elixir structs on steroids and is probably not what most people would expect.