Chapter 5: Functions and Clauses

Functions in Myst are a little different than most other languages. In the basic case, things are fairly normal:

1def add(a, b)
2  a + b
3end

Readers familiar with Ruby, Elixir, or other similar languages will likely recognize this syntax. The def keyword indicates that a function is being defined, add is the name of the function, a and b are the parameters of the function, and the body of the function is the expression a + b.

Myst has implicit return values just like Ruby, so the result of calling the function will be the result of a + b. For example:

1add(1, 2) #=> 3
2add(2, 4) #=> 6

Functions are useful for separating out logical components and creating building blocks to clean up code, avoid repetition, and better express more complex ideas.

Functions may also be called "methods". In practice, these terms are entirely interchangeable, where "method" is sometimes preferred for functions that are defined for instances of a type. This will be covered in a later chapter.

Pattern matching parameters

Myst also allows functions to define patterns in place of parameters. In fact, all parameters are really just the left-hand side of a pattern match. Giving a pattern as a parameter creates an expectation that the argument given to the function matches that value. For example:

1def add(0, n)
2  n
3end

This version of add expects that the first argument given to it will be 0. If the first argument in a call to this function is not 0, the function is not a match for the call, so it will not be called. In fact, an error will be raised saying that there is no match for the function with the given arguments.

Here are some examples of calls to this function:

1add(0, 3)   #=> 3
2add(0, 10)  #=> 10
3add(1, 2)   #=> raises a `FunctionMatchFailure`.

The first two calls succeed, because the expectation that the first argument is 0 is met, so the definition matches. The third call gives 1 as the first argument, which does not match 0, so the call fails.

A more complex example is extracting values from a Map argument:

1def foo(thing, {default: default, type: type})
2  # `default` and `type` are now available as variables.
3end

Multi-clause functions

A consequence of pattern matching parameters in function definitions is that a single function definition will not necessarily work for all calls. This was clearly shown with the add(1, 2) example above, and is not an accident.

In Myst, it's possible to define multiple different "versions" of a function with different parameters. Each "version" is called a clause, and all the clauses under a single name are collected into a full function object or functor (often just referred to as a function for simplicity).

The clauses of a function are stored in the order that they are defined, so when a call to a function is encountered, the language will run through the clauses in order, attempting to match all of the parameters. The first clause that matches all of the parameters will be used for the call. If no clauses match, an error will be raised, as shown above.

With this new information, a more complete add function from the above examples could be written with three clauses.

 1def add(0, n)
 2  :first_clause
 3end
 4
 5def add(n, 0)
 6  :second_clause
 7end
 8
 9def add(a, b)
10  :third_clause
11end

Now, add should work with any input:

1add(0, 1) #=> :first_clause
2add(1, 1) #=> :third_clause
3add(1, 0) #=> :second_clause

Note that because clauses are checked in order, the definition order is important. Consider the case where the third clause above is given first:

 1def add(a, b)
 2  :first_clause
 3end
 4
 5def add(0, n)
 6  :second_clause
 7end
 8
 9def add(n, 0)
10  :third_clause
11end
12
13add(0, 1) #=> :first_clause
14add(1, 1) #=> :first_clause
15add(1, 0) #=> :first_clause

Here, the first clause is always used, because a and b don't set any expectations on the arguments.

Another common use case for multiple clauses is working with different arities (the number of parameters for the clause). As described above, Myst attempts to match all of the parameters. If a clause has more parameters than there are arguments for the Call, or vice versa, it will fail to match and the next clause will be attempted.

Splat collection parameters

Similar to the splat collectors described in the pattern matching section earlier, parameters can also use splat collectors to collect extra arguments. The examples of getting the first and last elements of a list of arguments can be written in methods as follows:

 1def head(first, *_)
 2  first
 3end
 4
 5def tail(*_, last)
 6  last
 7end
 8
 9def ht(first, *_, last)
10  [first, last]
11end
12
13head(1, 2, 3) #=> 1
14tail(1, 2, 3) #=> 3
15ht(1, 2, 3)   #=> [1, 3]

The thing to note here is that the arguments are not given as a single list argument, but as individual arguments.

Type restrictions

Another way of defining a pattern for a parameter is to give an expected type, rather than an exact value. For example, to match any argument that is an integer, just use Integer as the parameter:

1def add(Integer, Integer)
2  :matched
3end
4
5add(1, 2) #=> matched
6add("hi", "") # does not match

Complex parameters

In the above examples, the parameters that define patterns or type restrictions don't capture the argument that is passed in, since the pattern is taking the place of the variable name.

To address this, Myst allows complex parameter definitions that can include patterns, a variable name, and a type restriction, all for the same parameter. A full example might look something like this:

1def foo([a, *_] =: args : List)
2  # do some work
3end

In this example, [a, *_] is the pattern for the parameter, followed by the match operator, then a variable name (in this case, args), and then a colon and a type name.

There are a few alternatives to this full syntax when all of the components are not needed. At a minimum, one of the components must be given. For example, a parameter that only has a pattern and a type can omit the =: name:

1def foo([a, *_] : List)
2  # ...
3end

With the introduction of types and modules, this syntax will become even more powerful, but will be covered later on, after those features have been covered independently.

Get Started

Introduction Values and Variables Basic Operations Flow Control Pattern Matching Functions and Clauses Modules Types and Self Blocks and Anonymous Functions Exception Handling Loading Code