Humus programs consist primarily of statements and expressions. Statements describe concurrent actions taken by a Humus program. Expressions describe pure (side-effect free) immutable values. Tokens are separated by whitespace or stand-alone punctuation
The Boolean constants
FALSE are distinct from all other values.
Integer constants consist of an optional sign followed by one or more digits. Radix integers begin with a decimal radix followed by a hash sign ‘
#‘ and end with one or more digits from the specified radix (e.g.:
16#03BB is GREEK SMALL LETTER LAMBDA in Unicode).
Symbols and Comments
A hash sign ‘
#‘ prefix causes the next token to be treated as a literal symbolic constant. But, if there is whitespace after the hash it begins a comment that terminates at the end of the line.
Identifiers consist of one or more non-whitepace, non-punctuation characters. Unlike many languages, they may begin with a digit — but they will be considered a number if they match the number grammar. In expressions, identifiers usually evaluate to the value of the variable they name. In a given context, variables are referentially transparent — they are bound only once, to an immutable value. Non-whitespace, non-punctuation characters include
Pairing and Grouping
Tuples are formed by the right-binding infix pairing operator, comma ‘
,‘. Parentheses ‘
(‘ and ‘
)‘ are used to group sub-expressions. By convention ‘
()‘ or ‘
NIL‘ denotes the empty tuple. Taken together, these rules mean that
1,2,3 is equivalent to
(1, (2, 3)) and
((1, 2), 3, NIL) is equivalent to
(1,2),(3,()). In essence, n-tuples are composed of pairs and
NIL is treated as a distinct non-pair value.
Abstractions are values that may be applied to an argument value to yield a result value. The form ‘
\pattern.expression‘ creates an abstraction value. The expression is not evaluated until later. When an abstraction is applied, an argument value is matched with the pattern, binding variables in the process. Then the expression is evaluated in the context of these new variable bindings to produce a result value. Since all identifiers are referentially transparent and denote immutable values, the specific evaluation strategy (eager, strict, lazy, etc.) is unspecified.
An application is the value that results from applying an argument value to an abstraction. The form ‘
abstraction(expression)‘ applies the expression argument to the abstraction to produce a result value. For example, the expression
(\(x,y).(y,x))(1,2,3) evaluates to
((2,3),1). Abstraction and application implement untyped lambda calculus. If the abstraction pattern does not match the argument, the result is the undefined value ‘
A block value captures, but does not execute, a group of statements. The form ‘
[ statement... ]‘ defines a block value. Block values are most often used, in conjunction with abstractions, to define the behavior of an actor. An actor’s behavior is an abstraction that is applied to a message to produce a block, which is then executed.
Patterns are used to match values and bind variables (identifiers) to values in a variety of contexts, including abstractions. Patterns can be constants, variables, tuples or value expressions.
Constants match identical values. Constants include
FALSE, numbers, symbols and
An identifiers matches any value and bind that value to the variable named by the identifier. The scope of this binding is dependent on the context in which the pattern is used. The identifier ‘
_‘ can be used as a place-holder in a pattern where a value is matched but no binding is created.
Patterns may be combined into tuple-patterns using the pairing operator ‘
,‘ and the grouping operators ‘
(‘ and ‘
)‘. Components of a tuple-value are matched with the corresponding components of the tuple-pattern. For example, the equation ‘
(a, d) = (1, 2, 3)‘ matches successfully and binds a to
1 and d to
The pattern ‘
$identifier‘ substitutes the value of identifier into the pattern as a constant. The pattern ‘
$(expression)‘ evaluates the expression and substitutes the resulting value into the pattern as a constant. Value expressions are the only way to reference the values of existing variables rather than bind the variables to new values during matching.
Humus statements are executed in parallel to maximize concurrency.
The form ‘
CREATE identifier WITH behavior‘ creates a new actor with the specified behavior and binds the identity of the new actor to identifier. Actor identities are necessarily unique, distinct from all other values, and initially known only the creator and the new actor. The behavior of an actor is a function (abstraction) that maps a message to a statement block. The resulting statement block is executed in the context of the actor, where the keyword ‘
SELF‘ refers to the identity of the actor.
The form ‘
SEND message TO actor‘ sends an asynchronous message to the specified actor. The message may contain any values, including actor identities. The sender may include their own identity with the ‘
SELF‘ keyword. Note that messages, like all values, are immutable.
The form ‘
BECOME behavior‘ designates a new behavior for processing subsequent messages to the current actor. If an actor’s behavior does not execute a
BECOME, the current behavior will be re-used to process the next message.
The form ‘
LET pattern1 = pattern2‘ makes a pattern matching assertion. This is not an assignment. It is more like a mathematical equation. If the patterns match, any variables in the patterns are bound to their matched values. If the patterns do not match, an exception is thrown. Note that since these are patterns, not expressions, the value prefix ‘
$‘ must be used to access the value of identifiers. Any attempt to re-bind a variable to a different value is treated as a match failure.
The form ‘
THROW expression‘ throws an exception with the value of expression. If an actor behavior throws an exception, all concurrent activities performed by the behavior have no effect. The current message is discarded and subsequent messages are processed with the current behavior.
If an expression is encountered where a statement is expected, the expression is evaluated to produce a block value. The block is then executed in parallel with all other statements. Each block defines a nested scope for identifiers.
Conditionals are expressions that select values based on pattern matching. They are often used in statement contexts to produce blocks for conditional execution.
IF … ELIF … ELSE …
The form ‘
IF equation expression‘ tries to resolve the pattern matching equation, possibly resulting in variable bindings, as with
LET. If the match succeeds, the expression is evaluated in the context of these new bindings. If the match fails, the value is undefined ‘
?‘. The form ‘
IF equation expression1 ELSE expression2‘ evaluates expression2, without any new bindings, if the equation fails to match. The form ‘
IF equation1 expression1 ELIF equation2 expression2 ...‘ tries each equation in sequence, evaluating the expression corresponding to the first match, or the optional
ELSE clause if there is no match.
CASE … OF … END
In the common case of matching a value against multiple patterns,
CASE may be more expressive. The form ‘
CASE value OF pattern:expression ... END‘ attempts to match the value with each pattern in sequence, evaluating the expression corresponding to the first match. As with
IF, the expression is evaluated in a context with any new bindings established by the matching pattern. The pattern ‘
_‘ matches any value and introduces no bindings, so it can be used like an ‘