# fugue API An object system for Janet, inspired by CLOS. --- ## background The Janet language provides a simple form of object orientation using tables and prototypes: ### methods Janet tables can have *methods* by storing functions at a keyword on the table. When called with method syntax `(:method-name object)`, the call is translated into a function call to that function with the object itself as the first argument. ### inheritance Janet provides prototypal inheritance. Any table may have a prototype set, whereupon any value access at that table that returns `nil` will recurse upwards to the table's prototype. # fugue `fugue` provides a more powerful set of commands that extend the existing OO dynamics of Janet with a more elaborate interface and set of features. ## `fugue/defproto` `defproto` is the main entrypoint to fugue, allowing users to define *Prototypes*. These are Janet tables with some additional metadata included, as well as a constructor method added at `:new`. `defproto` takes arguments that govern how new instances are created, as well as allocation rules, ie, the configuration determining which object fields should be populated at the instance level and which at the prototype level. ## `fugue/defgeneric` / `fugue/defmethod` `defgeneric` provides the interface for creating and managing *Generic Functions*---functions which can be extended for additional Prototypes. Generic functions may have a default behaviour, which is then specialized for Prototypes via `defmethod`. Methods can be specialized for a single argument---the first argument---and child prototypes can inherit methods from their ancestors. They can also call ancestor methods by invoking `(prototype-method)` within their own method bodies. ## `fugue/defmulti` `defmulti` allows the creation of *Multimethods*. Unlike Generic Functions/Single Methods, Multimethods can be defined for the types of all of their arguments, not just the first one. They are also not limited to fugue Prototypes, being definable over any Janet type or a fugue Prototype. Also unlike Single Methods, they are *not* inheritable; a multimethod defined for an ancestor Prototype will not be selected for any descendent prototype instances. ## fugue [@](#), [Root](#Root), [Root*?](#Root-1), [Root?](#Root-2), [allocate](#allocate), [declare-open-multi](#declare-open-multi), [defgeneric](#defgeneric), [defmethod](#defmethod), [defmulti](#defmulti), [defproto](#defproto), [extend-multi](#extend-multi), [fields](#fields), [get-type-or-proto](#get-type-or-proto), [match](#match), [multimethod-types-match?](#multimethod-types-match), [new-Root](#new-Root), [prototype?](#prototype), [with-slots](#with-slots), [with-slots-as](#with-slots-as) ## @ **macro** | [source][1] ```janet (@ proto x &opt y) ``` Compile-time Prototype field checking. Accepts two forms: `(@ SomePrototype :some-field)` - Translates into `:some-field`, if `some-field` is defined on `SomePrototype`. `(@ SomePrototype some-object :some-field)` - Asserts (as above) at compile time that `some-field` is defined on `SomePrototype`; at runtime, checks that `some-object` is a descendent of `SomePrototype` and if so, translates to `(some-object :some-field)`. [1]: fugue.janet#L797 ## Root **table** | [source][2] ```janet @{:_init :_meta @{ :prototype-allocations @{} :getters @{} :object-type :prototype :fields () :instance-defaults @{}} :_name "Prototype" :new } ``` Root of the Fugue object hierarchy. [2]: fugue.janet#L40 ## Root*? **function** | [source][3] ```janet (Root*? obj) ``` Proto ancestor predicate: return if `obj` is a descendent of Root. [3]: eval#L-1 ## Root? **function** | [source][4] ```janet (Root? obj) ``` Proto instance predicate: return if `obj` is an instance (that is, a direct child) of Root. [4]: eval#L-1 ## allocate **function** | [source][5] ```janet (allocate obj key value) ``` Allocation-aware put. If `obj` has inherited an allocation to a specific prototype for this key, then `fugue/allocate` will put `value` at `key` in the appropriate prototype, and it will be inherited by all descendents of that prototype. [5]: fugue.janet#L362 ## declare-open-multi **macro** | [source][6] ```janet (declare-open-multi name) ``` Declare an open multimethod, ie, one that can be extended. Extending an open multimethod (see `extend-multi`) from any other environment makes the case extension available wherever the multimethod has been imported. [6]: fugue.janet#L676 ## defgeneric **macro** | [source][7] ```janet (defgeneric name & rest) ``` Define a generic function. When this function is called, if the first argument has a method corresponding to the name of the function, call that object 's method with the arguments. Otherwise, evaluate `body`. [7]: fugue.janet#L399 ## defmethod **macro** | [source][8] ```janet (defmethod name proto args & body) ``` Simple single-dispatch method definition. Roughly equivalent to `put` ing a function directly into a prototype. Defines a few symbols for reference in the body of the method. - `__parent` - Bound to the parent of `proto`. - `__super` - Bound to the method at `name` within `__parent`. [8]: fugue.janet#L417 ## defmulti **macro** | [source][9] ```janet (defmulti name multi-types args & body) ``` Define a multimethod based on all the arguments passed to the function. Example usage : ``` > (defproto Foo ()) > (defmulti add [Foo] [f] (put f :value 1)) > (defmulti add [:number] [x] (+ x 1)) > (defmulti add [:string] [s] (string s "!")) > (def a-foo (:new Foo)) > (add a-foo) @Foo{:value 1 :_meta @{:object-type :instance}} > (add 1) 2 > (add "s") "s!" ``` `defmulti` takes a sequence of type-or-prototypes, and builds a function which will check its arguments against those types (as well as all other ones specified in other `defmulti` calls to the same function name), and execute the function body for the matching type signature. In addition to type names or prototypes, you can use the symbol `_` or keyword `:_` as a wildcard that means "match any type". For instance, ``` repl:2:> (defmulti cat [:string :_] [s1 s2] (string s1 s2)) repl:3:> (cat "hello " "world!") "hello world!" repl:4:> (cat "hello " 42) "hello 42" repl:5:> (cat 42 "hello") error: could not apply multimethod to args (42 "hello") in cat [repl] on line 2, column 1 in _thunk [repl] (tailcall) on line 5, column 1 ``` Defining a multimethod with the signature `[:string :_]` will match on any two arguments if the first one is a string. A multimethod without wilcards will be preferred to one with one in the same position. For instance, if we define an additional multimethod: ``` repl:8:> (defmulti cat [:string :number] [s n] (string s " #" n)) ``` Then that more specific method will be preferred and the wildcard will be a fallback if the specific one doesn't match: ``` repl:10:> (cat "hello " @"world") "hello world" repl:12:> (cat "hello" 100) "hello #100" ``` [9]: fugue.janet#L600 ## defproto **macro** | [source][10] ```janet (defproto name parent-name & fields) ``` Object prototype definition. ## Usage `name` should be any symbol. The resulting prototype will be named after it. `parent-name` is required; it can be an existing prototype, *or* some null-ish value. If null-ish (`nil` or `()` should make the most sense...) the parent of the prototype will be set to `fugue/Root`. `fields` should be 0 or more pairs of the following format: ` ` Where `field-name` is a field to define on the prototype and `field-attributes` is a struct describing the field. The following field attributes are currently recognized: - `:default`: provide a default value for all new instances of this prototype - `:init?`: if truthy, then this field will be a required parameter to the prototype 's constructor - `:allocation`: if `:prototype`, then `fugue/allocate` will always act on the prototype when putting this field. - `:allocate-value`: this field will have this attribute set at the prototype, so that any children without their own values will inherit it. - `:getter`: specify a name for the defined function to access this field (by default, has the same name as the field). Specify `false` to prevent a getter from being defined. `defproto` will define a getter function for each of the defined fields, unless `:getter` is false. `defproto` will also create a `:new` method in the created prototype. This will take as positional arguments all of the fields specified as `init?`, and then accept in `&keys` format any other attributes to set on this object. The special method `:_init` will be called as the last step in the `:new` constructor. It can be defined for a prototype (see `defmethod`) to take a new instance and to make any arbitrary mutations on the instance or prototype as part of object instantiation. By default it simply returns the instance. The value provided to a field's `:default` entry will be inserted directly to the instance. Thus, mutable/referenced terms like tables and arrays will be shared amongst all instances. In cases where you want to insert a new term for each new instance, use the `_init` method to put a value at that field. If `fields` is of an odd length, the last element will be treated as a prototype attributes struct. There is currently one valid prototype attribute: - `:constructor` : Set the name of the defined function that calls `:new`. If false, no additional constructor will be defined. By default, will be set to `new-`. --- An example usage: ``` repl:43:> (fugue/defproto Dog () name {:allocate-value "Fido"}) repl:44:> (fugue/defproto Pekingese Dog size {:default "Extremely Small"}) repl:45:> (fugue/defmethod speak Dog [self] (string "My name is " (self :name))) repl:46:> (fugue/defmethod speak Pekingese [self] (string (prototype-method self) " and I am " (self :size))) repl:47:> (speak (:new Pekingese)) "My name is Fido and I am Extremely Small" ``` [10]: fugue.janet#L259 ## extend-multi **macro** | [source][11] ```janet (extend-multi multi multi-types args & body) ``` Extend an open multimethod (see `declare-open-multi`) using the same syntax as `defmulti`. See that function's documentation for full usage reference. Whenever a case is added to `multi`, that case is available wherever the multimethod is imported. [11]: fugue.janet#L692 ## fields **function** | [source][12] ```janet (fields obj) ``` Return all the defined fields for `obj` and its prototype hierarchy. [12]: fugue.janet#L50 ## get-type-or-proto **function** | [source][13] ```janet (get-type-or-proto obj) ``` Return the prototype of `obj`, if it has one, otherwise the keyword output of `type`. [13]: fugue.janet#L173 ## match **macro** | [source][14] ```janet (match x & cases) ``` Prototype-aware version of `match`. Introduces one new case form: - `(@ )`: Will pattern match against an instance of `prototype-name`. Additionally, will validate at compile-time that every key in `dictionary` is a field that's present on the specified prototype. [14]: fugue.janet#L831 ## multimethod-types-match? **function** | [source][15] ```janet (multimethod-types-match? args arg-types) ``` Check to see if the types `args` match the sequence `arg-types`, according to multimethod rules (ie, following prototype membership and using `:_` as a fallback) [15]: fugue.janet#L525 ## new-Root **function** | [source][16] ```janet (new-Root & rest) ``` Constructor for Root. Return a new object with Root as the prototype. [16]: eval#L-1 ## prototype? **function** | [source][17] ```janet (prototype? obj) ``` Is `obj` the result of a `defproto ` call? [17]: fugue.janet#L354 ## with-slots **macro** | [source][18] ```janet (with-slots proto obj & body) ``` Anaphoric macro with transformed getter/setters. Introduces two useful forms for referring to `obj`. It introduces a *reference symbol* - `@` by default (see `with-slots-as `to specify the symbol). The pattern `(@ )`, where `` is a symbol, is transformed into `(obj (keyword ))`, if and only if `` is defined for `proto`, so that `(@ name)` or its setter form `(set (@ name) foo)` do the right thing. The reference symbol by itself is introduces as a reference to `obj`. Returns `obj`. --- Example : ``` repl:2:> (defproto Foo nil name {:default "Jane Doe"}) repl:4:> (with-slots Foo (new-Foo) (set (@ name) "Cosmo Kramer") (print (@ name)) (print (Foo? @))) Cosmo Kramer true @Foo{:_meta @{:object-type :instance} :name "Cosmo Kramer"} ``` [18]: fugue.janet#L742 ## with-slots-as **macro** | [source][19] ```janet (with-slots-as proto obj as & body) ``` Anaphoric macro with transformed getter/setters. Specifies `as` as the reference symbol for `with-slots`. See `with-slots` documentation for more details. [19]: fugue.janet#L779