On this page:
describe
thing
send
thing?
is-a?
thing=?
Self
extends
inherit

3.10 Things🔗ℹ

Things are Heresy’s definable data structures. Unlike the objects of most object-oriented languages, which often exist to hold and carry mutable state and actions with which to change that state, Things are immutable. A Thing, once sprung to life, cannot itself be altered, but can be called with the correct syntax to return a new Thing with different internal values for its internal data fields.

Things are essentially functions, lambdas specifically, with predefined syntax arguments. They are first class values, and can be passed freely just as any other data, but can also be passed arguments to either return the values of their fields, return a new Thing, or to employ any functions contained within the thing.

Things can also optionally be given predicate types. Predicate typing is a form of typing in which types are defined by a predicate function, in other words a function which given a value, will return either true or false. In this way, any kind of type validation can be specified so long as it can be programmed as a function which returns a Boolean value. Things check their values against these types at both declaration, when the object is first described or instantiated, and at assignment of new values, ie. when the copy syntax is used to generate a new thing from the old one. If you attempt to describe or copy a thing whose values do not match its predicate types, the program will throw an error and indicate what field did not match its type.

syntax

(describe Name)

(describe Name (field [(type? args ...)] value) ...)
(describe Name
          extends super-thing
          (field [(type? args ...)] value) ...)
(describe Name
          extends super-thing
          inherit (id ...)
          (field [(type? args ...)] value) ...)
Defines a new type of Thing, given Name. By convention, Things are generally named in uppercase, though this is not required by the syntax. Each field is an internal name and external symbol, which is mapped to the given value. Anonymous functions (fn) can be assigned as values to Thing fields, and those functions can access the fields of the Thing by name.

Optionally, after the field name, a type predicate can be provided. Type predicates are automatically "curried", ie. treated as a partial function with the initial arguments following the given type?, and expecting the result to be a single argument function that returns True or False. Things which are not given a type are automatically given the type any?, which returns True for any value.

If the extends option is provided, the new Thing extends super-thing, inheriting it’s fields and methods (unless they are overridden). If the inherit option is provided with it, then the ids are available as bindings within the method expressions. Typed things can be extended from untyped things and vice versa; the fields from the parent will inherit their types from the parent, unless overridden by creating a new field with the same name and a new type signature (or no signature, as the case may be). Note that parent things are never modified by their children.

Examples:
> (describe Project
            (name   "Destroy the world")
            (id     90)
            (budget 432000000))
> (Project 'budget)

432000000

> (describe Employee
            (name     (string?) "Dave")
            (id       (number?) 42)
            (dept     (symbol?) 'it)
            (projects (list-of? number?) '(23 90 45)))
> (Employee 'name)

"Dave"

> (Employee '(* * "sales" *))

Thing encountered type error in assignment: dept must be

(symbol?)

> (def fn age-req? (age) (and (< 17 age) (> 45 age)))
> (describe Henchman extends Employee
            (weapon (symbol?)  'AK-47)
            (age    (age-req?) 64))

Thing encountered type error in declaration: age must be

(age-req?)

syntax

(thing)

(thing (field [(type? args ...)] value) ...)
(thing extends super-thing
       (field [(type? args ...)] value) ...)
(thing extends super-thing
       inherit (id ...)
       (field [(type? args ...)] value) ...)
Just like fn produces an anonymous function, thing produces an anonymous Thing.

If there is a Thing defined as Name:

(Name)

(Name symbol)
(Name 'fields)
(Name alist)
(Name pattern)
Once a Thing has been described or bound to a name by other means, that Name is bound locally as a function, and can thus be called with special syntax to return its contents or to return a new copied Thing. In more detail, these syntaxes are as follows:

(Name)

Returns an association list containing the contents of the Thing, ie. a list in the form of: '((field value) ...)

(Name 'fields)

Returns a list of symbols for the fields contained within the Thing. Note that the symbol 'fields takes precedent over the field names within, in order to prevent overwriting this syntax.

(Name symbol)

Returns the value of the field associated with symbol, the quoted form of the field name described when the Thing type was first declared. Will return an error if no such named field is found. If the value associated with symbol is a function, this expression can be used as the operating function of a further expression like so:

Examples:
> (describe Lord-Cthulhu (eat (fn (x) (print (& "Devours " x)))))
> ((Lord-Cthulhu 'eat) "Dave")

Devours Dave

(Name alist)

 
alist = `(pair ...)
     
pair = `(field value)
Returns a copy of the Thing, with new values assigned to the fields as indicated by the provided associative list. All values not listed will copy over their values intact. The copy will type-check the values of each field assigned.

Examples:
> (describe Beeb (model (symbol?) 'B) (ram (number?) 32) (cpu (string?) "m6502"))
> (def BeebPlus (Beeb '((model B+) (ram 64))))
> (BeebPlus)

'((model B+) (ram 64) (cpu "m6502"))

(Name pattern)

 
pattern = `(sub-pat ...)
     
sub-pat = *
  | value
Returns a copy of the Thing, with new values according to the pattern passed to the original Thing. pattern must be a quoted list of either '*s or values, in order according to the fields of the Thing as originally defined (so the first sub-pat matches the first field, the second to the second field, and so on). A '* in a field indicates that the value is copied in-tact, while a value becomes the new value of the field in that position. For example:

Examples:
> (describe Santa
            (size 'fat)
            (sleigh 'ready)
            (sack 'full))
> (def Santa-after-Christmas (Santa `(* * empty)))
> (Santa-after-Christmas)

'((size fat) (sleigh ready) (sack empty))

procedure

(send Thing symbol arg ...)  any

  Thing : thing?
  symbol : symbol?
  arg : any
An alternate syntax for accessing functions within Things, send calls the function named by (Thing symbol) with the given arguments and returns the result.

procedure

(thing? v)  boolean?

  v : any?
Returns True if v "looks like" a Thing, or False if it doesn’t. thing? employs a duck-typing method, checking the object for the expected properties of a Thing, so it is possible, albeit unlikely, to fool it. Specifically it checks first if v is a fn?, then checks the returns for the default internal methods of all Things, and its internal hash value.

procedure

(is-a? Type Thing)  boolean?

  Type : thing?
  Thing : thing?
Returns True if Thing is an instance of Type. This will return True if Thing is the same kind as Type, or if Thing is derived from Type, as by extends. This is done by comparing the internal '_ident field of Type to both the '_ident and '_parents fields of Thing.

procedure

(thing=? thing1 thing2)  boolean

  thing1 : thing?
  thing2 : thing?
Returns True if thing1 and thing2’s fields are equal? to each other, according to the internal hash values generated from their fields, after first checking that both things are the same type according to is-a?.

syntax

(Self ....)

Self is the self-referring identifier for a Thing, allowing for functions within Things to call the Thing itself. Note that if it is only the values of the other fields, this is not necessary, as fields are defined as local names within the context of the Thing, and thus can be referred to simply by name.

syntax

extends

can only be used within a describe or thing form.

syntax

inherit

can only be used within a describe or thing form.