Although it is not apparent from what we have said so far, Factor has object-oriented features, and many core words are actually method invocations. To better understand how objects behave in Factor, a quote is in order:
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind."
-Alan Kay The term object-oriented has as many different meanings as people using it. One point of view - which was actually central to the work of Alan Kay - is that it is about late binding of function names. In Smalltalk, the language where this concept was born, people do not talk about calling a method, but rather sending a message to an object. It is up to the object to decide how to respond to this message, and the caller should not know about the implementation. For instance, one can send the message
map both to an array and a vector, but internally the operation will be handled differently.
The binding of the message name to the method implementation is dynamic, and this is regarded as the core strength of objects. As a result, fairly complex systems can evolve from the cooperation of independent objects that do not mess with each other's internals.
To be fair, Factor is very different from Smalltalk, but still there is the concept of classes, and generic words can defined having different implementations on different classes.
Some classes are builtin in Factor, such as
string,
boolean,
fixnum or
word. Next, the most common way to define a class is as a
tuple. Tuples are defined with the
TUPLE: parsing word, followed by the tuple name and the fields of the class that we want to define, which are called
slots in Factor parlance.
Let us define a class for movies:
TUPLE: movie title director actors ;
This also generates setters
>>title,
>>director and
>>actors and getters
title>>,
director>> and
actors>>. For instance, we can create a new movie with
movie new
"The prestige" >>title
"Christopher Nolan" >>director
{ "Hugh Jackman" "Christian Bale" "Scarlett Johansson" } >>actors
We can also shorten this to
"The prestige" "Christopher Nolan"
{ "Hugh Jackman" "Christian Bale" "Scarlett Johansson" }
movie boa
The word
boa stands for 'by-order-of-arguments'. It is a constructor that fills the slots of the tuple with the items on the stack in order.
movie boa is called a
boa constructor, a pun on the Boa Constrictor. It is customary to define a most common constructor called
<movie>, which in our case could be simply
: <movie> ( title director actors -- movie ) movie boa ;
In fact, boa constructor are so common, that the above line can be shortened to
C: <movie> movie
In other cases, you may want to use some defaults, or compute some fields.
The functional minded will be worried about the mutability of tuples. Actually, slots can be declared to be read-only with
{ slot-name read-only } . In this case, the field setter will not be generated, and the value must be set a the beginning with a boa constructor. Other valid slot modifiers are
initial: - to declare a default value - and a class word, such as
integer, to restrict the values that can be inserted.
As an example, we define another tuple class for rock bands
TUPLE: band
{ keyboards string read-only }
{ guitar string read-only }
{ bass string read-only }
{ drums string read-only } ;
: <band> ( keyboards guitar bass drums -- band ) band boa ;
together with one instance
"Richard Wright" "David Gilmour" "Roger Waters" "Nick Mason" <band>
Now, of course everyone knows that the star in a movie is the first actor, while in a rock band it is the bass player. To encode this, we first define a
generic word GENERIC: star ( item -- star )
As you can see, it is declared with the parsing word
GENERIC: and declares its stack effects but it has no implementation right now, hence no need for the closing
;. Generic words are used to perform dynamic dispatch. We can define implementations for various classes using the word
M: M: movie star actors>> first ;
M: band star bass>> ;
If you write
star . two times, you can see the different effect of calling a generic word on instances of different classes.
Builtin and tuple classes are not all that there is to the object system: more classes can be defined with set operations like
UNION: and
INTERSECTION:. Another way to define a class is as a
mixin.
Mixins are defined with the
MIXIN: word, and existing classes can be added to the mixin like so:
INSTANCE: class mixin
Methods defined on the mixin will then be available on all classes that belong to the mixin. If you are familiar with Haskell typeclasses, you will recognize a resemblance, although Haskell enforces at compile time that instance of typeclasses implement certain functions, while in Factor this is informally specified in documentation.
Two important examples of mixins are
sequence and
assoc. The former defines a protocol that is available to all concrete sequences, such as strings, linked lists or arrays, while the latter defines a protocol for associative arrays, such as hashtables or association lists.
This enables all sequences in Factor to be acted upon with a common set of words, while differing in implementation and minimizing code repetition (because only few primitives are needed, and other operations are defined for the
sequence class). The most common operations you will use on sequences are
map,
filter and
reduce, but there are many more - as you can see with
"sequences" help.