Declarations & Declarators
There is no “top level” in Pinafore. A program file consists of an expression. A module consists of a list of declarations.
Declarators
Declarators output bindings to be used in the scope of declarations and expressions.
“Let” Declarators
let
-declarators output bindings from their contained declarations.
Declarations in let
-declarators have sequential, not recursive scope.
For example:
let a=3; b=4 in a+b
Here a+b
is an expression, a=3
and b=4
are declarations, and let a=3; b=4
is a declarator.
This declarator outputs bindings for a
and b
to the scope of the expression a+b
.
Declarators can also output bindings to declarations:
let
let a=3; b=4 in c=a+b;
in c+1
Here the declarator let a=3; b=4
outputs bindings for a
and b
to the declaration c=a+b
.
The declarator let let a=3; b=4 in c=a+b
outputs a binding for c
, but not a
or b
, to the expression c+1
.
A declarator can also be converted into a declaration using the end
keyword:
let
let a=3; b=4 end;
c=a+b;
in c+a+b
Here let a=3; b=4 end
and c=a+b
are declarations.
The outer declarator outputs bindings for a
, b
, and c
to the expression c+a+b
.
Recursive “Let” Declarators
For recursive declarations, use let rec
to make a recursive “let”-declarator.
Declarator declarations and namespace declarations are not allowed inside recursive blocks.
let
let rec
datatype P of
Mk (Q -> P);
end;
datatype Q of
Mk (P -> Maybe Q);
end;
end;
let rec
fact = match
0 => 1;
n => n * fact (n - 1);
end;
end;
in fact 12
“With” Declarators
“With” declarators copy bindings from a given namespace into the current namespace. See Namespaces below.
“Import” Declarators
“Import” declarators import bindings from a module, which can either be one of the standard libraries, or a module file.
Declarations
These are the different kinds of declarations:
Value declarations
Type declarations
Subtype declarations
Standalone declarator declarations
Declarator-qualified declarations
Namespace declarations
Expose declarations
Value Declarations
Value declarations are of the form <pattern> = <expression>
.
Infix Operators
New operators can be declared with parentheses, like this:
let
(&$$&) = fn a,b => a - b;
in 57 &$$& 22
The parsing “infixity” of the operator is determined by its name (regardless of namespace) according to the table, and is “(A x B) x C” level 10 for other names.
Type & Subtype Declarations
See Types.
Standalone Declarator Declarations
Any declarator can be turned into a declaration by appending end
.
let
let rec
x = y;
y = 4;
end;
namespace N of
z = 1;
end;
with N end;
in x + y + z;
Declarator-Qualified Declarations
Declarators can qualify declarations just as they can qualify expressions.
let
namespace N of
z = 1;
end;
with N in x = z;
in z;
Namespaces & Namespace Declarations
A namespace is a space for declarations. Each namespace is either the root namespace, or a namespace with a name within another namespace. This name starts with an upper-case letter. Thus, each namespace can be identified by a list of zero or more names.
In a given scope, all declaration names are in some namespace. The full name of a declaration consists of the name and the namespace. Full names are unique in the scope.
Note that name qualification goes in order specific-to-general, the reverse of most other languages.
So variable x
inside namespace N
inside namespace M
is x.N.M.
.
Referring to Declarations
In a given scope, there is a current namespace.
References to namespaces are either relative or absolute. Absolute namespace references consist of names separated by dots, with a trailing dot. These names specify the namespace directly.
Relative namespace references consist of names separated by dots. These are searched in the current namespace, and then all ancestors of the current namespace.
For example,
namespace Metadata.Image.
refers to the namespaceMetadata
within the namespaceImage
within the root namespace.namespace Metadata.Image
refers to the namespaceMetadata
within the namespaceImage
within searched namespaces.
Likewise, references to full names are either relative or absolute. Absolute full name references consist of names separated by dots, with a trailing dot. These names specify the namespace directly, and then the name within the namespace.
Relative full name references consist of names separated by dots. These names are searched in the current namespace, followed by all ancestor namespaces.
For example, if the current namespace is B.A.
:
async.Task.
is alwaysasync.Task.
, that is, it refers to the declarationasync
within the namespaceTask
within the root namespace.async.Task
refers to the first found of these:async.Task.B.A.
,async.Task.A.
,async.Task.
.
Current Namespace
All declarations are placed within the current namespace.
A namespace
declaration specifies the current namespace for the declarations it contains, relative to the existing current namespace.
Mapping Namespaces
A with
declarator aliases names into different namespaces.
For example:
with P
maps the contents of namespaceP
into the current namespace.with P (a,b)
mapsa.P
andb.P
into the current namespacewith Q.P
maps the contents of namespaceQ.P
into the current namespace.with P (namespace Q)
maps namespaceQ.P
into the current namespace asQ
.with P (a,b) as N
mapsa.P
andb.P
into namespaceN
, so they can be referred to asa.N
andb.N
.
Example
let
namespace A of
p = 3;
namespace B of
q = 4;
end;
end;
namespace C of
s = 6;
with B.A. end;
t = q + 12;
end;
with A end;
s = p + q.B + s.C.A;
in body
In this example, the scope for body
contains declarations with these full names, with these values:
p.A. = 3
q.B.A. = 4
s.C.A. = 6
t.C.A. = 16
p. = 3
q.B. = 4
s. = 13
At the point at which q.B.A.
is declared, references such as x
will be searched in this order:
x.B.A.
(the current namespace)x.A.
(parent of the above)x.
(parent of the above)
Expose Declarations
Expose declarations provide a simple way of hiding declarations.
Only the specified names will be exposed from the declaration, although subtype relations (which are nameless) will always be exposed.
Expose declarations can be used within let
-expressions for more fine-grained hiding.
let
let
# Mk.LowerCaseText not exposed
datatype LowerCaseText of Mk Text end;
subtype LowerCaseText <: Text = fn Mk.LowerCaseText t => t;
toLowerCase: Text -> LowerCaseText;
toLowerCase t = Mk.LowerCaseText $ textLowerCase t;
in expose LowerCaseText, toLowerCase;
# Outside the above block, there is no way to create a LowerCaseText
# that is not lower-case text.
in toLowerCase "Hello"
It’s also possible to expose everything in a namespace:
let
let
x = 1; # not exposed
namespace N of
p = x + 2;
q = 3;
end;
r = 4;
in expose namespace N, r;
in N.p + N.q + r