Skip to main content

Routines

Collections of sequential expressions. Routines are your basic parentheses-based grouping mechanism.

(one; two)

This example shows two labels in a routine. Each expression separated by ; is executed and the routine evaluates to the last expression. Any expressions preceding the last one are discarded. If you try to assign a routine to a label or field, you will only store the result, not the routine itself.

Subroutines

When you want to store a routine and execute it at a later time, place the parentheses within angle brackets (<()>) and it will be converted into a reference routine. Reference routines are known as subroutines in Rhumb. The result of evaluating a subroutine is the stored reference to an anonymous routine.

<(foo; bar)>

To use a subroutine, you would supply a label to this value and then invoke it at a later time. These subroutines are just reusable code but there is a way to supply unlabeled arguments:

fib .= <(?1 << 2 => ?1 !> fib(?1 -- 1) ++ fib(?1 -- 2))>
fib(10) %= 55

Arguments

The prefix operator ? is used to access the raw arguments passed to the current subroutine.

  • ?1 ... ?N: Accesses the Nth argument (1-based index).
  • ?0: Accesses the full list of arguments as a Map.
  • Behavior: Accessing an index that was not passed evaluates to ___ (Empty).
Note: Base Expression Usage in Subroutines

Whenever a subroutine uses a base expression, it causes the subroutine to shift it's default return value from the last expression to the base map. If the user doesn't want this behavior, they must explicitly return a value at the end of the subroutine. This is how Rhumb differentiates between constructors and non-constructor subroutines.

% returns the last expression's value
multiply .= [x] -> x ** 2

% returns ! (the base map)
User := [name] -> !\name := name

When adding "methods" (subroutines in map fields), you do not need to bind them because a subroutine in a map field automatically references the map it is called from. For example, when <User>\<set-age> is called, the subroutine knows to use <User> as its base map. If you explicitly bind it however, then it will use the bound map as its base map.

<User>\set-address := [street; city; state; zip] -> (
!\street := street
!\city := city
!\state := state
!\zip := zip
)
<User>\set-mailing-address :=
<User>\set-address !! <User>\mailing

Rhumb has a shorthand way to bind a subroutine directly to the map it is defined in:

Player .= [
name .. 'Hero'
health :: 100

% Here, `!` refers to the map that calls it
take-damage .. [amount] ->
!\health := !\health -- amount

% Here, `!` refers to the `Player` map
heal .. [amount] !> !\health := !\health ++ amount
]

GameEngine .= [
% The engine has its own separate health pool
health :: 9999
% The Empty Value, used to store a deferred subroutine
on-tick :: ___
]

% ------------------------------------------------------
% SCENARIO A: The Hijacked Context
% ------------------------------------------------------
% We store the reference to the unbound subroutine
GameEngine\on-tick := <Player>\take-damage

% The GameEngine invokes the subroutine.
% Because it was defined with ->
% the base (!) becomes the GameEngine!
GameEngine\on-tick(10)
% RESULT: The Player is unharmed
% but the GameEngine's health drops to 9989!

% ------------------------------------------------------
% SCENARIO B: The Safe Context
% ------------------------------------------------------
% We store the reference to the bound subroutine
GameEngine\on-tick := <Player>\heal

% The GameEngine invokes the subroutine.
% Because it was defined with !>
% the base (!) remains strictly locked to the Player.
GameEngine\on-tick(20)
% RESULT: The Player's health correctly increases to 120.

Invocation

Imagine we have a subroutine with a label of baz. You would invoke by just referencing it. If you want to supply arguments, you can include a postfix set of parentheses but they are not required when there are no arguments.

baz % same as
baz() % this

You can supply multiple arguments to the subroutine by separating values using the ; operator.

baz(1; two)
Why no comma operator?

Because commas are part of numbers as per some cultural conventions. Commas aren't as intuitive as periods are (due to existing technical culture) when parsing labels, so they are only used within number tokens as either a decimal separator or for grouping thousands.

Functions

When you want to explicitly name the arguments that are supplied to a subroutine, provide them with a submap. A submap is normally delinaeated with a <[...]> but the -> function operator will do the referencing (<...>) automatically.

pythag .= [a; b; c] -> a^^2 ++ b^^2 // c^^2
equiv-subroutine .= <(?1^^2 ++ ?2^^2 // ?3^^2)>

For a submap or function parameters, you must supply a surrounding op of [] at the least. You can even slurp or concatenate two submaps together in a manner:

person .= <[first; last; age]>
employee .= <[grade; title; id]>

% 'person' and 'employee' are just argument labels here
accessWrong .= [person; employee] -> (...)

% The ref. ops `<...>` around a label inside of a submap
% literal treats the label as a reference to a value
% instead of an argument label. This allows you to keep
% the structure of a submap in tact.
access1 .= [<person>; <employee>] -> (
employee\grade << 23 =>
#access-denied(
person\first;
person\last;
employee\id
)
)

% To reference all fields from the submaps into the
% current routine's scope and avoids the need for writing
% the labels out with the `\` operator in the body of the
% routine:
access2 .= [<person>\*; <employee>\*] -> (
grade << 23 =>
#access-denied(first; last; id)
)

% Instead of using the reference operator, you can also
% use the concat operator '&&'. This concatenates the two
% submaps together and returns a new submap. Because we're
% using a routine `(...)`, the resulting submap is then
% used as the function's parameter list.
access3 .= (person && employee) -> (
grade << 23 =>
#access-denied(first; last; id)
)

Here, you can see how parameter lists are first-class constructs that captures the spirit of named arguments, records and scope.