Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
274 lines (273 sloc) 15.3 KB
extends ../../tmpl/base.jade
block body
.container
| Guides • Learn Standard ML • Chapter 3
h1 Expressions and Variables
.subheader
a(href="https://github.com/eatonphil/ponyo/blob/master/site/static/app/templates/guides/learn-standardml/expressions-and-variables.jade") Source
.container
.sidebar-container
.row
.col-md-6
h3 Expressions
p.
An expression is a chunk of code that returns a value. An expression
can always be assigned to a variable. On the other hand, a statement
is a chunk of code that does not return a value. A statement cannot be
assigned to a variable. Most parts of Standard ML are expressions,
not statements. Expressions consist of one of <the following:
ul
li Literals (e.g. <code>1</code>, <code>"foo"</code>)
li Variable references (e.g. <code>a</code>, <code>theVariable</code>)
li Expressions combined by an infix operator (e.g. <code>a + b</code>, <code>1 + theVariable</code>)
li If blocks (e.g. <code>if a then b else c</code>)
li Case blocks (e.g. <code>case a of 2 => true | 3 => false</code>)
li Lambda functions (e.g. <code>fn a => a</code>)
li Function calls (e.g. <code>split "foo" "o"</code>)
li Let blocks (e.g. <code>let val i = 1 in i end</code>)
li Local blocks (e.g. <code>local val i = 1 in i end</code>)
li Compound expressions (e.g. <code>1; a + b</code>)
li Any expression in parenthesis (e.g. <code>(1)</code>, <code>(theVariable)</code>)
p The following are <strong>not</strong> expressions:
li Val declarations (e.g. the <code>val i = 1</code> part of <code>let val i = 1 in i end</code>)
li Named function declarations (e.g. <code>fun a b => b</code>)
li Type declarations (e.g. <code>type a = int</code>, <code>datatype b = Int of int | String of string</code>)
li Structures (declarations and references)
li Signatures (declarations and references)
li Functors (declarations and references)
h4 Literals
p.
The following types can be expressed as literal values:
ul
li Unit <code>()</code>
li Nil <code>nil</code>
li Integers (e.g. <code>1</code>, <code>~1</code>)
li Reals (e.g. <code>1.0</code>, <code>~1.0</code>)
li Strings (e.g. <code>"my string"</code>)
li Characters (e.g. <code>'f'</code>, <code>'\n'</code>)
li Booleans <code>true</code> and <code>false</code>
li Tuples (e.g. <code>(1, 2)</code>, <code>(x, 4.0, (12, 1))</code>)
li Lists (e.g. <code>[1, 2]</code>, <code>[(1, 2), (3, 4)]</code>)
li Records (e.g. <code>{ foo=1, bar=2.0 }</code>)
p.note.
Some Standard ML implementations (including MLton, but not Poly/ML)
additionally have support for vector literals (e.g. <code>\#[1, 2, 3]</code>)
as part of Successor ML, informally agreed upon extensions to the standard.
h4 Parenthesis, semi-colons and compound expressions
p.
Semi-colons are mostly optional, except for in compound expressions.
But semi-colons are often necessary to disambiguate the end of a function
call. Here is an example:
pre
code.
let
val foo = fn a => a
val goo = fn b => b
in
foo "bar"
goo "baz"
end
p.
In this example, even though Standard ML knows that <code>foo</code> and <code>goo</code>
only take one argument, the compiler will still complain as it tries
to apply three arguments to <code>foo</code>: <code>foo "bar" goo "baz"</code>. You will need
a semi-colon to disambiguate this:
pre
code.
let
val foo = fn a => a
val goo = fn b => b
in
foo "bar";
goo "baz"
end
p.note.
Standard ML officially requires there be no trailing semi-colon in
a compound expression. Simarily, a comma must not follow the last
element in a tuple, list, or record. Some Standard ML implementations
(including MLton, but not Poly/ML) relax this requirement as part of
Successor ML.
p.
The <code>in ... end</code> block actually takes a single expression. But compound
expressions are expressions too and provide a way to evaluate multiple
expressions for side-effects while only returning the value of the last
expression.
p.
Parenthesis are typically allowed anywhere, surrounding an expression.
But outside of the <code>in ... end</code> block of a let block, you will need to
surround a compound expression in parenthesis to disambiguate:
pre
code.
> val i = (1 + 2; 8);
val i = 8: int
h4 Conditional logic
p.
There are two primary ways to conditionally evaluate expressions:
<code>if ... then ... else</code> and <code>case ... of ...</code> blocks.
Both return a value. An if block operates only on booleans. There is
no coercion of types like in C so you cannot test an int directly. You
must first compare the int to something to produce a boolean value.
Here is a simple example of an if block that returns <code>3</code>:
pre
code.
> if true then 3 else 12;
val it = 3: int
p.
The two simplest builtin operators for boolean logic are <code>andalso</code>
and <code>orelse</code>. Here is an example of their use.
pre
code.
> val twelve = if true andalso false then 3 else 12;
val twelve = 12: int
> val eleven = if false orelse true then 11 else 13;
val eleven = 13: int
> twelve + eleven;
val it = 25: int
p.
If you need an if block inside the then block, you will need to wrap it
in parenthesis to disambiguate the ending else. But if an if block follows
another if block in the <code>else</code> section, you don't need
parenthesis:
pre
code.
> val i = if true then 1 else if false then 11 else 14;
val i = 1: int
> val j = if true then (if false then 12 else 14) else 15;
val j = 14: int
p.
Case blocks provide generic pattern matching. You can match
on any values and can destructure here like anwhere else. Here
are a few examples:
pre
code.
> val j = 12;
val j = 12: int
> val i = case j of
# 11 => true
# | 12 => false
val i = false: bool
> val s = case "foo" of
# "bar" => 1 + 2
# | "foo" => 45
val s = 45: int
p.
Case blocks have the same ambiguities as if blocks when it comes to
nested case blocks. If a case block follows another case block as
the ending case, it doesn't need to be wrapped in parenthesis. Otherwise,
it does.
h3 Variables
p.
Variables are declared in the top-level with the <code>val</code> keyword. The
type of the variable will (typically) be inferred without explicit
annotation.
pre
code.
> val i = 1;
val i = 1: int
p.
Outside of the top-level, variables must be declared inside a
<code>let ... in ... end</code> block. Only variable declarations can occur inside
the first sub-block. And only expressions can occur inside the second
sub-block. Semi-colons between variable declarations are optional, but
semi-colons between expressions are not.
p.
The simplest let block contains no variable declarations and returns a
single value.
pre
code let in 1 end
p.
The value of this let expression is <code>1</code>. A more complex let block will
declare variables. These variables are in scope immediately after
declaration and within the second sub-block. These variables are not
valid outside the let block.
pre
code.
let
val i = 1
val g = i + i
in
i;
g
end
p.
The result of evaluating this expression would be 2. While we reference
<code>i</code> in the second sub-block, the resulting value is not used and so is
discarded. Only the value of <code>g</code> is returned by the entire
<code>let ... in ... end</code> block.
h4 Discarding variables
p.
The underscore is a special variable marker that can be used to discard
a value or values. It can be used multiple times even within the same
variable declaration:
pre
code.
> val _ = 12;
> val [_, _, a] = [1, 2, 3];
val a = 3: int
p.
If you try to re-use any other variable name like that, the compiler will
error out:
pre
code.
> val [a, a, b] = [1, 2, 3];
poly: : error: a has already been bound in this declaration
Found near val [a, a, b] = [1, 2, 3]
Static Errors
h4 Shadowing variables
p.
Variables in Standard ML can be shadowed. In most languages, this happens
within a nested block scope. This is the case in Standard ML as well.
For instance, the following example will produce the value <code>5</code>:
pre
code.
let
val i = 1
in
let
val i = 2
in
i + i
end
+ i
end
p.
Variables can also be shadowed within the same scope.
On the one hand, this can be a useful way to approximate procedural
algorithms. On the other hand, you can get subtle bugs because you
can erase a variable's previous value (as long as the type remains
the same) without the compiler complaining. The following example
will compile with no warnings and produce <code>2</code>:
pre
code.
let
val i = 1
val i = i + i
val i = 2
in
i
end
p I would avoid doing this in general.
h4 Mutable values
p.
Variables in Standard ML are immutable. However not all values in
are immutable. <code>ref</code> cells are the simplest example of mutable
values. Here are is a full example using <code>ref</code> cells:
pre
code.
let
val a = ref 1
val b = a
val c = (!b) + 3
in
a := 3;
c * (!a) + (!b)
end
p.
The result of this expression is (1 + 3) * (3) + (1) = 15.
The <code>!</code> operator dereferences the ref cell to get its value.
The type of <code>a</code> and <code>b</code> is <code>ref int</code>.
p.
Arrays are also mutable values (as opposed to vectors), but we'll get into
those in a later chapter.
p
a(href="/guides/learn-standardml/functions") Chapter 4. Functions