Learn: Operators

Every min program needs operators to:

There are two types of operators: symbols and sigils.

Symbols

Symbols are the most common type of operator. A min symbol is a single word that is either provided by one of the predefined min modules like dup or . or defined by the user. User-defined symbols must:

It is possible to define operator symbols using the operator symbol. The following min program defines a new symbol called square that duplicates the first element on the stack and multiplies the two elements:

 (
   symbol square
   (num :n ==> num :result)
   (n dup * @result)
 ) operator
 ;; Calculates the square of n.

The operator symbol provides way to: * Specify the name of the symbol operator (square in this case) * Specify a signature to identify the type of the input and output values (in this case, the operator takes a numeric input value and produces a numeric output value). Also, note how inputs and outputs are captured into the n and result symbols in the signature quotation and then referenced in the body quotation. * Specify a quotation containing the code that the operator will execute.

Also, symbol operator definitions can be annotated with documentation comments (starting with ;; or wrapped in #|| ... ||#)) so that a help text can be displayed using the help symbol.

Using the lambda operator

Sometimes you just want to bind a piece of code to a symbol to reuse it later, typically something simple and easy-to-read. In these cases, you can use the lambda operator (or the ^ sigil). For example, the previous square operator definition could be rewritten simply as the following.

 (dup *) ^square

Note that this feels like using define, but the main difference between lambda and define is that lambda only works on quotations doesn’t auto-quote them, so that they are immediately evaluated when the corresponding symbol is pushed on the stack.

Also note that unlike with operator, symbols defined with lambda: * have no built-in validation of input and output values. * do not support the return symbol to immediately end their execution. * have no built-in stack pollution checks.

%tip Tip

You can use lambda-bind to re-set a previously set lambda.

Sigils

Besides symbols, you can also define sigils. min provides a set of predefined sigils as abbreviations for for commonly-used symbols.

A sigil can be prepended to a double-quoted string or a single word (with no spaces) which will be treated as a string instead of using the corresponding symbol.

For example, the following executes the command ls -al and pushes the command return code on the stack:

 !"ls -al"`

Currently min provides the following sigils:

'
Alias for quote.
:
Alias for define.
*
Alias for invoke.
@
Alias for bind.
^
Alias for lambda.
>
Alias for save-symbol.
<
Alias for load-symbol.
/
Alias for dget.
%
Alias for dset.
?
Alias for help.
!
Alias for system.
&
Alias for run.
$
Alias for get-env.

Besides system sigils, you can also create your own sigils. Unlike system sigils however, user defined sigils:

Sigils can be a very powerful construct and a way to reduce boulerplate code: you can define a sigil to use as you would use any symbol which requires a single string or quoted symbol on the stack.

Like symbols, sigils can be defined with the operator operator, like this:

 (
   sigil j
   (string :json ==> a :result)
   (json from-json @result)
 ) operator

This definition will add a j sigil that will process the follwing string as JSON code, so for example:

 j"{\"test\": true}"

…will push the following dictionary on the stack:

{true :test}

Also, sigil definitions can be annotated with documentation comments (starting with ;; or wrapped in #|| ... ||#)) so that a help text can be displayed using the [help`](/reference-lang#op-help) symbol.

Auto-popping

Typically, but not always, operators push one or more value to the stack. While this is typically the desired behavior, in some cases you may want to keep the stack clear so in these cases you can append a ! character to any symbol to cause the symbol pop to be pushed on the stack immediately afterwards.

 "test" puts  ;Prints "test" and pushes "test" on the stack.
 "test" puts! ;Prints "test" without pushing anything on the stack.

Operator signatures

When defining symbols and sigils witb the operator operator, you must specify a signature that will be used to validate and captuee input and output values:

 (
   symbol square
   (num :n ==> num :result)
   (n dup * @result)
 ) operator

In this case for example tbe square symbol expects a number on the stack, which will be captured to tbe symbol n and it will place a number on the stack which needs to be bound in the operator body to the symbol result.

In a signature, a type expression must precede the capturing symbol. Such type expression can be:

Note

If the operator you are defining doesn’t require any input value or doesn’t leave ang output value on the srack, simply don’t put anything before or after the ==> separator, respectively. For example, the signature of the puts! operator could be written like (a ==>).

Type classes

Besides standard base types, you can define your own type classes to express custom constraints/validations for operator input and output values.

Consider the following type class definition validating a quotation containing strings:

 (
   typeclass strquot
   (quot :q ==> bool :o)
   (q (string?) all? @o)
 ) ::

The operator operator can be used to define a symbol prefixed with typeclass: (typeclass:strquot in this case) corresponding to a type class that can be used in operator signatures in place of a type, like this:

 (
   symbol join-strings
   (strquot :q ==> str :result)
   ( 
      q "" (suffix) reduce @result
   )
 )

This operator will raise an error if anything other than a quotation of strings is found on the stack.

Tip

typeclass:-prefixed symbols are just like ordinary shmbols: they are lexically scoped, they can be sealed, unsealed and deleted.

Capturing lambdas

You can also specify a lambda to be captured to an output value, like this:

 (
   symbol square
   (==> quot ^o)
   (
     (dup *) ~o
   )
 ) ::

Essentially, this allows you to push a lambda on the stack from an operator.

Note that:

Type expressions

When specifying types in operator signatures or through the expect operator, you can specify a logical expression containing types and type classes joined with one of the following operators:

Suppose for example you defined the following type classes:

(typeclass fiveplus
    (int :n ==> bool :o)
    (
      n 5 > @o
    )
) ::

(typeclass tenminus
    (int :n ==> bool :o)
    (
      n 10 < @o
    )
) ::

(typeclass even
    (int :n ==> bool :o)
    (
      n 2 mod 0 == @o
    )
) ::

You can combine them in a type expression as following:

(symbol test
    (!even|tenminus&fiveplus :n ==> bool :o)
    (
      true @o
    )
) ::
4 test  ; error
6 test  ; true
11 test ; true 

Type aliases

As you can see, type expressions can quickly become quite long and complex. To avoid this, you can define type aliases using the typealias operator.

For example, you can create an alias of part of the type expression used in the previous example, like this:

'tenminus&fiveplus 'five-to-ten typealias

(symbol test
    (!even|five-to-ten :n ==> bool :o)
    (
      true @o
    )
) ::

Note that:

Generics

min supports generics in operator signatures. in other words, you can define a custom type alias on-the-fly directly in an operator signature, like this:

(
  symbol add
  ((str|num|quot :t) :a t :b ==> t :result)
  (
   (a type "str" ==)
     (a b suffix @result return)
   when
   (a type "num" ==)
     (a b + @result return)
   when
   (a type "quot" ==)
     (a b concat #result return)
   when
  )
) ::

In this case, t is set to the type union stribg|num|quot, and the add method above can be use too sum two numbers or join two strings or quotations.

Note that the value of t is evaluated to the type of the first value that is processed. In other words, the following programs will work as expected:

 3 5 add ;outputs 8

 "hello, " "world" ;outputs "hello, world"

while tbe fullowing will raise an error, because the value of t from num to quot within the same operator use:

 12 "test" add ;raises an error

Constructors

The operator operator can also be used to create constructor symbols. A constructor is a particular type of operator that is used to create a new typed dictionary.

Consider the following example:

 (
   constructor point
   (num :x num :y ==> dict :out)
   (
     {}
       x %x
       y %y
     @out
   )
 ) ::

The operator above creates a point constructor symbol that can be used to create a new dict:point typed dictionary by popping two numbers from the stack:

 2 3 point ; {2 :x 3 :y ;point}

Tip

Except for some native symbols, constructors represent the only way to create new typed dictionaries. The more validations you perform in a constructor, the most effective checking for a specific type using the type? operator will be, as type? only checks if a specific type annotation is present on a typed dictionary, nothing else.

→ Continue to Quotations