8.12

1.7 Operators🔗ℹ

The operator form defines a prefix, infix, or postfix operator for expressions, similar to a function definition:

operator (x <> y):

  Posn(x, y)

> 1 <> 2

Posn(1, 2)

operator (<<>> x):

  Posn(x, x)

> <<>> 3

Posn(3, 3)

operator (x <<<>>>):

  Posn(x, x)

> 4 <<<>>>

Posn(4, 4)

An “operator” name does not have to be a shrubbery operator. It can be an identifier:

operator (x mod y):

  x - math.floor(x / y) * y

> 10 mod 3

1

The operator form is followed by parentheses and then a block. Inside the parentheses, there must be exactly two or three terms, and the first, middle, or last term must be an operator or identifier to define. The arguments can be described by binding patterns, but in that case, they may need parentheses around the pattern to ensure that they form a single term in next to the operator being defined:

operator ((x :: Int) <> (y :: Int)):

  Posn(x, y)

> 1 <> "apple"

<>: argument does not satisfy annotation

  argument: "apple"

  annotation: Int

An operator can be defined for both infix and prefix behavior in much the same way that functions can be defined to accept one or two arguments, and there can be multiple cases with different binding patterns for infix, prefix, or both:

operator

| ((x :: Int) <> (y :: Int)):

    Posn(x, y)

| (<> (x ::Int)):

    Posn(x, x)

| (<> "origin"):

    Posn(0, 0)

> 1 <> 2

Posn(1, 2)

> <> 3

Posn(3, 3)

> <> "origin"

Posn(0, 0)

A combination of prefix and postfix is also allowed, but infix and postfix cannot be mixed. For the unusual case of infix plus postfix, you would have to resort to more general parsing tools, as we’ll see in Expression Macros.

Operator precedence is declared in relationship to other operators when the operator is defined. With no precedence defined, <> cannot appear near an arithmetic operator like *:

> 1 <> 2 * 3

*: explicit parenthesization needed;

 found operators without declared precedence or associativity

  operator kind: expression operator

  earlier operator: <>

> 1 <> (2 * 3)

Posn(1, 6)

The initially defined operators mostly have the usual precedence: * and / are stronger than + and -, while + and - have the same predence and are left-associative. The * and / operators have the same precedence as long as * appears only to the left of /,

A precedence declaration in operator takes the form of keyword blocks at the start of the operator’s body. The possible keyword options for prefix operators are ~weaker_than, ~stronger_than, ~same_as, or ~same_as_on_left. For infix operators, those options apply, as well as ~same_as_on_right and ~associativity. Operators listed with keywords like ~weaker_than can be grouped on lines however is convenient.

operator (x <> y):

  ~weaker_than: * / + -

  ~associativity: ~right

  Posn(x, y)

> 1 <> 2 * 3

Posn(1, 6)

> 1 <> 2 <> 3

Posn(1, Posn(2, 3))

Use the keyword ~other in ~weaker_than, ~stronger_than, or ~same_as to declare a precedence relationship for operators not otherwise mentioned.

When multiple cases for an operator are provided using |, then only the first case for prefix, infix, or postfix can have options. Precedence for prefix and infix/postfix can be independent in that form. Alternatively, put the operator name directly after operator, then start a block that contains precedence and associativity that applies to all cases. Similar to this notation for fun, a result annotation can be written before the block.

operator <> :: Posn:

  ~weaker_than: * / + -

  ~associativity: ~right

| (x <> y):

    Posn(x, y)

| (<> n):

    Posn(n, n)

> 1 <> 2 * 3

Posn(1, 6)

> 1 <> 2 <> 3

Posn(1, Posn(2, 3))

> <> 4 * 5

Posn(20, 20)

An operator can be exported the same as an identifier:

export:

  <>

On the import side, to refer to an operator that has a prefix, put the operator after . in parentheses:

import:

  "posn.rhm"

 

1 posn.(<>) 2

If the point of an operator is terseness, however, an import prefix may defeat the point. Using a library that supplies operators may be one reason to expose an imported name. To selectively make an operator accessible without it import’s prefix, use the expose import modifier or a dotted import:

import:

  "posn.rhm".(<>)

 

1 <> 2