The Scaly Programming Language

Chapter 6

Definitions

Overview

Definitions declare new types that can be used throughout a program. Types can have properties, methods, and nested definitions.

Functions, Procedures, and Operators

A routine is introduced by one of three keywords, and that keyword — not the spelling of the name — decides how the routine behaves:

  • function declares a pure function: its inputs are borrowed and immutable, and it returns a result without side effects. A function is applied prefix, by writing its name before a parenthesized argument list.

  • procedure declares a routine that may mutate its inputs and have side effects (builders, I/O).

  • operator declares an operator: a pure routine applied infix between its two operands (or prefix, for a unary operator).

The three identifier forms from the lexical chapter — alphanumeric, operator-character, and single-quoted string identifiers — are interchangeable as names here. A function may be named with operator characters, and an operator may be named with letters or a quoted string; what makes one a function and the other an operator is the declaration keyword, never the first character of the name.


function 'add seven'(n: int) returns int      ; string-named function
    n + 7

function ++(n: int) returns int               ; operator-char-named function
    n + 1

let a 'add seven'(35)                          ; applied prefix -> 42
let b ++(41)                                   ; applied prefix -> 42
let c ++ 41                                    ; one argument needs no parentheses -> 42
let c ++ 41                                    ; one argument needs no parentheses -> 42
    

Operators may be declared at the top level, not only as members of a type. Like functions, they accept any of the three name forms:


operator <+>(left: int, right: int) returns int    ; operator-char name
    left + right

operator 'divided by'(left: int, right: int) returns int   ; quoted name
    left / right

let c 40 <+> 2              ; infix -> 42
let d 84 'divided by' 2     ; infix -> 42
    

Built-in arithmetic, comparison, and logical operators carry their usual precedences. A custom operator that no level of the operation names — a text or single-quoted name, or a symbol such as == — binds most weakly of all, on a level of its own below ||, so the built-in arithmetic in an expression combines before it. For example, given an operator 'minus' that subtracts its right operand from its left, 20 'minus' 3 + 5 parses as 20 'minus' (3 + 5) and yields 12.

An operator's right parameter may be declared lazy: the caller's operand is handed over unevaluated, and the operator's body is put in where the operator stands. The standard library declares and and or on bool this way, so the right operand runs only where the left one does not decide:


operator and(left: bool, right: lazy bool) returns bool
    if left: right else false

let ok p <> null and p.x = 7   ; p.x is read only where p is there
    

The right operand is planned where the body puts it, so a null test on the left proves its name for it, as an if proves it for its branch. A free function and a method may declare a lazy parameter too: the call is the body put in where it stands, ordinary arguments evaluated once in the order they are written, a lazy one where the body names it:


function or_else(v: int, fallback: lazy int) returns int
    if v <> 0: v else fallback

let n or_else(cached, expensive())   ; expensive() runs only where cached is 0
    

Definitions

Definitions declare new types that can be used throughout a program.

Structure Definitions

The define keyword creates a new structure type with named properties.

InputResult
define Point(x: int, y: int)
Point(3, 4)
{"_type":"Point","x":3,"y":4}
define Point(x: int, y: int)
Point(3, 4).x
3
define Point(x: int, y: int)
let p Point(3, 4)
p.x + p.y
7
define Point(x: int, y: int)
define Rectangle(origin: Point, width: int, height: int)
Rectangle(Point(0, 0), 10, 5).width
10

Union Definitions

Unions define types with multiple variants. Each variant can optionally carry a value.

InputResult
define Option union(Some: int, None)
Some(5)
{"_type":"Option","_variant":"Some","value":5}
define Option union(Some: int, None)
None
{"_type":"Option","_variant":"None"}
define Result union(Ok: int, Err: String)
Ok(42)
{"_type":"Result","_variant":"Ok","value":42}
define Result union(Ok: int, Err: String)
Err("error")
{"_type":"Result","_variant":"Err","value":"error"}
define Option union(Some: int, None)
Option.Some(5).value
5

Function Names

A routine's role is set by its declaration keyword, not by how its name is spelled. A function may be named with ordinary letters, with a single-quoted string identifier (which may contain spaces), or with operator characters; in every case it is an ordinary function, applied by writing its name before a parenthesized argument.

InputResult
function 'add seven'(n: int) returns int
    n + 7
'add seven'(35)
42
function ++(n: int) returns int
    n + 1
++(41)
42

Operator Definitions

The operator keyword declares an operator, applied infix between its two operands. Like functions, operators may be named with operator characters or with a text or single-quoted name. Operators may be declared at the top level, not only inside a type. An operator that no level of the operation names, a text-named one among them, binds more weakly than every level, so the arithmetic combines first.

InputResult
operator <+>(left: int, right: int) returns int
    left + right
40 <+> 2
42
operator 'plus'(left: int, right: int) returns int
    left + right
40 'plus' 2
42
operator 'divided by'(left: int, right: int) returns int
    left / right
84 'divided by' 2
42
operator 'minus'(left: int, right: int) returns int
    left - right
20 'minus' 3 + 5
12

Lazy Parameters

A parameter declared lazy before its type receives its argument unevaluated: the routine's body is put in where the call stands, and the argument runs where the body names it, and only there. This is how the standard library declares and and or, and it works for an ordinary function as well.

InputResult
function or_else(v: int, fallback: lazy int) returns int
    if v <> 0: v else fallback
procedure loud() returns int
{
    print "evaluated"
    7
}
or_else(5, loud())
5
function or_else(v: int, fallback: lazy int) returns int
    if v <> 0: v else fallback
or_else(0, 6 * 7)
42