Maps & Prototypes
Rhumb follows a prototype-based object-oriented programming model, similar to the Self programming language or JavaScript. In Rhumb, the fundamental object is the Map.
Maps as Objects
A Map is a collection of fields (key-value pairs). Maps serve what other languages would call objects.
point.1 .= [
x :: 10
y :: 20
% A method using the base operator '!' to access self
distance .. [other] !> 2 ^/ ( !\x - other\x ) + ( !\y - other\y )
]
point.2 .= [x::10; y::20]
point.1\distance(point.2) //?= 2
Shorthand Syntax
Inside a map literal, the . and : prefix operators serve as shorthand for assigning variables to fields of the same name.
- Immutable (
.):[.foo]is equivalent to[foo..foo]. It assigns the variablefooto the fieldfooimmutably. - Mutable (
:):[:foo]is equivalent to[foo::foo]. It assigns the variablefooto the fieldfoomutably.
This shorthand works for subfields as well. For example, [.@bar] is equivalent to [@bar..bar].
Outside of a map literal, these prefix operators have different meanings:
.freezes a value (Make Immutable).:creates a copy of a value (Clone).
Slots and Lookup
In Rhumb, fields act as slots. When you access a field on a map (e.g., my-map\some-field), Rhumb performs a lookup:
- Local Lookup: It checks if
my-mapcontains a field namedsome-field. - Delegation (Inheritance): If the field is not found, Rhumb looks into the map's subfields (prototypes).
Subfields (Prototypes)
Subfields are special fields in a map that act as parents or prototypes. If a slot isn't found in the map, the lookup continues in these subfields. You can define a subfield using the @ prefix.
% The prototype
vehicle .= [
type :: 'Generic Vehicle'
describe .. [] -> "This is a $(!\type)"
]
% The child map inheriting from vehicle
car .= [
% .@vehicle syntax adds 'vehicle' as a subfield/prototype
.@vehicle
@vehicle .. vehicle % Equivalent to .@vehicle
% Overriding a field
type :: 'Car'
]
car\describe %= "This is a Car
In this example:
car\describeis called.- Rhumb looks for
describeincar. It's not found. - Rhumb checks the subfield
vehicle.describeis found there. - The function is executed. Crucially, the base (
!) remains bound tocar, so!\typeresolves to'Car'.
The Base (!)
When a function is defined with the -> operator (Unbound Function), it acts as a method. Inside the function, the ! operator gives you access to the base map (the receiver of the message) where the function is called. This is equivalent to self or this in other languages.
counter .= [
count :: 0
increment .. [] -> !\count := !\count ++ 1
]
counter\increment
console\log(counter\count) % 1
When a function is defined with the !> operator, it is a bound function and the base map is bound to where it was defined. The base map is effectively a part of the function definition, not passed in when the function is invoked.
counter .= [
count :: 0
increment :: [] -> !\count := !\count ++ 1
]
otherCounter .= [
shadowCount :: 0
increment .. [] !> ( !\shadowCount := !\shadowCount ++ 1)
]
counter\increment := otherCounter\increment
counter\increment % Because increment was bound to otherCounter, it uses otherCounter's base
counter\count %= 0
otherCounter\shadowCount %= 1
Implicit Base Return
If a subroutine accesses the base (!), it will automatically return the base upon completion, unless a bare signal (#) is explicitly sent.
% '!' is accessed, so it is returned implicitly
set-a .= [val] -> (
!\a := val
% #(!) is not needed, already default behavior
)
% Explicit signal overrides implicit return
set-b .= [val] -> (
!\b := val
#(val) % Return 'val' instead of '!'
)
Multiple Inheritance (Traits)
Since a map can have multiple subfields, Rhumb supports multiple inheritance (or traits).
identifiable .= [
id :: '12345'
get-id .. [] -> !\id
]
namable .= [
name :: 'Unknown'
get-name .. [] -> !\name
]
% Inherit from both
user .= [
.@identifiable
.@namable
name :: 'Alice'
]
user\get-name %= 'Alice'
user\get-id %= '12345'
Accessing Subfields Directly
Sometimes you want to bypass the standard lookup or access a specific prototype directly (like super calls).
-
@Operator: Access a specific named subfield.child@parent\some-method -
[@]Operator: Access all subfields as a single map.child[@]\some-method
Methods vs Functions
->(Function): A standard function. Does not bind!until it is called, and then it is bound to the object it was called on. Useful for inherited methods.!>(Bound Function/Method): Binds!to the object it was defined in. A shortcut for defining static methods.
Dynamic Dispatch
Because maps are dynamic, you can add or change behavior at runtime.
robot .= [ .@vehicle ]
robot\type := 'Robot'
% Add a new method dynamically, binding it to robot
robot\say-hello := [] !> "Beep boop"
robot\say-hello %= "Beep boop"
Manual Binding (!!)
You can manually bind a function to a specific object using the !! operator. This is useful for reusing functions across different objects without inheritance or when you want to execute a standalone function within a specific context.
identify .= [] -> !\name\upper-case
speak .= [] -> (
!\identify = identify % equivilent to `<identify> !! !`
"Hello, I'm $(!\identify)"
)
me .= [ name .. 'Jake' ]
you .= [ name .. 'Reader' ]
(<identify> !! me)() %= 'JAKE'
(<identify> !! you)() %= 'READER'
(<speak> !! me)() %= "Hello, I'm JAKE"
(<speak> !! you)() %= "Hello, I'm READER"
Summary
- Maps are the sole object type.
- Fields hold state or behavior.
- Subfields (
@) allow delegation (inheritance). !accesses the current object (self).->defines functions that do not bind!; instead!is set when the function is called.!>defines bound functions that bind!to where the function was defined.