Operators and Identifiers

Eucalypt distinguishes two different types of identifier, normal identifiers, like x, y, α, א, ziggety-zaggety, zoom?, and operator identifiers like *, @, &&, , , ⊙⊙⊙, <> and so on.

It is entirely a matter of the component characters which category an identifier falls into. Normal identifiers contain letters (including non-ASCII characters), numbers, "-", "?", "$". Operator identifiers contain the usual suspects and anything identified as an operator or symbol in unicode. Neither can contain ":" or "," or brackets which are special in eucalypt.

Any sequence of characters at all can be treated as a normal identifier by surrounding them in single quotes. This is the only use of single quotes in eucalypt. This can be useful when you want to use file paths or other external identifiers as block keys for instance:

home: {
  '.bashrc': false
  '.emacs.d': false
  'notes.txt': true
}

z: home.'notes.txt'

Normal identifiers

Normal identifiers are brought into scope by declarations and can be referred to without qualification in their own block or in more nested blocks:

x: {
  z: 99
  foo: z //=> 99
  bar: {
    y: z //=> 99
  }
}

They can be accessed from within other blocks using the lookup operator:

x: {
  z: 99
}

y: x.z //=> 99

They can be overridden using generalised lookup:

z: 99
y: { z: 100 }."z is {z}" //=> "z is 100"

They can be shadowed:

z: 99
y: { z: 100 r: z //=> 100 }

But beware trying to access the outer value:

name: "foo"
x: { name: name } //=> infinite recursion

Accessing shadowed values is not yet easily possible unless you can refer to an enclosing block and use a lookup.

Prefix operators

Some operators are defined as prefix (unary) operators rather than infix (binary) operators. These bind tightly to the expression that follows.

For example, the operator is a tight-binding prefix form of head:

xs: [1, 2, 3]
first: ↑xs  //=> 1

Because it binds tightly (precedence 95), it works naturally in pipelines without parentheses:

xs: [[1, 2], [3, 4]]
result: xs map(↑)  # map head over list of lists

Other prefix operators include ! and ¬ for boolean negation, and for numeric negation.

Operator identifiers

Operator identifiers are more limited than normal identifiers.

They are brought into scope by operator declarations and available without qualification in their own block and more nested blocks:

( l -->> r): "{l} shoots arrow at {r}"

x: {
  y: 2 -->> 3 //=> "2 shoots arrow at 3"
}

...and can be shadowed:

(l !!! r): l + r

y: {
  (l !!! r): l - r
  z: 100 !!! 1 //=> 99
}

But:

  • they cannot be accessed by lookup, so there is no way of forming a qualified name to access an operator
  • they cannot be overridden by generalised lookup

Operator Precedence Table

Operator precedence determines how operator expressions are parsed when parentheses are omitted — higher numbers bind more tightly. This table is verified against named_precedence in src/core/metadata.rs and the operator declarations in lib/prelude.eu:

PrecNameAssocOperatorsDescription
95--prefixHead (tight prefix)
90lookupleft. (built-in)Property lookup
90lookupleft~Safe key lookup (null-propagating)
90callleft(built-in)Function call
88bool-unaryprefix!, ¬Boolean negation
88bool-unarypostfixNot-null check (true if not null)
88----, ;Composition
85expright^Power
85exp--!! (nth)Indexing
80prodleft*, /, ÷, %Multiplication, floor division, precise division, floor modulo
75sumleft+, -Addition, subtraction
60shift--(shift ops)Reserved
55bitwise--(bitwise ops)Reserved — (cons) also uses this numeric level, precedence: 55 in lib/prelude.eu
50cmpleft<, >, <=, >=Comparison
45appendleft++List concatenation
45appendleft<<Deep merge
42mapleft<$>Functor map
40eqleft=, !=Equality
35bool-prodleft&&, Logical AND
30bool-sumleft||, Logical OR
20catleft(catenation)Juxtaposition / pipeline
15clauseleft=>, Cond clause builder (condition => result)
10applyleft@Function application
5metaleft//, //<<, //=, //=>, //=?, //=?>, //!Metadata and assertions

User-defined operators default to left-associative, precedence 50. Set custom values via metadata:

` { associates: :right precedence: :sum }
(x +++ y): x + y

See Agent Reference §2 for the full named-level list and more examples.