Expression: value and function
Type of expression: e : t (expression e has type t)
Type of function: e : t_1 \to t_2 (expression e has argument type
t_1 and return type
t_2)
val
Declarationsidentifiers: variable names
val <varname> : <type> = <expr>
an output will be the reverse of a declaration
val <varname> = <value> : <type>
if the name is not provided,
it
will be used as default name
Expression terminated with a semicolon will be evaluated at terminal.
For homework, any semicolon will lose points
If you don't evaluate an expression, the terminal will give =
fun
Declarationsval x : int = 10 (* *)
val y : int = x * x (* [10/x] *)
val z : int = x + y (* [100/y, 10/x] *)
Note that comments reflects the bindings before each line's execution. Therefore they are the scope of the line when execute.
If you bind to the same variable twice, the first binding exist prior to the second binding. See code example from Lecture_001
You can think of expressions as "something eventually will be a value" and declarations as "commands" or "actions" for the computer to do work.
infix operator: a function that can be insert between two arguments
You can use
infix 9 ^
to declear infix operator^
where9
denotes a priority of evaluation.
^
: string concat
binding: each use of val is one binding
declaration:
can be consist of multiple bindings
two declaration written together can be thought as one declaration
scope of declaration: consists of other declarations that can be used
"If we can declare some “thing” using a declaration, then we say that “thing” is within the scope of that declaration."
val
expressionWithout expecting type
val <varname> = <expr>
With type annotation
val <varname> : <type> = <expr>
You can also write
val <varname> = <expr> : <type>
val <varname> : <type> = <expr> : <type>
let
expressionlet val < varname > = < expr1 > in < expr2 > end
Variables in SML refer to values, but are not re-assignable like variables in imperative programming languages. Each time a variable is declared, SML creates a new binding and binds that variable to a value. This binding is available, unchanged, throughout the scope of the declaration that introduced it. If the name was used before, the new binding shadows the previous one: the old binding is still around, but new uses of the variable refer to the most recent one.
Again, see code example from Lecture_001
val ( x : int , y : int ) = (3 ,4)
val z : int * int = (3 ,4)
val 3 : int = 1 + 2;
val 10 : int = 5 + 5;
val " hello , world " = " hello , " ^ " world " ;
Values: always bind themselves, and value can only be bind to itself
SML/NJ will not output anything because we didn’t create any new bindings; 3
always binds 3
, "hello , world "
always binds " hello , world "
, etc.
use "file_name.sml";
will copy everything in the file to terminal, output will be like:
- use " playground . sml ";
[ opening playground . sml ]
...
val it = () : unit
-
Example Functions
fun fst ( x : int , y : int ) : int = x (* has type fst: int * int -> int *)
fun snd ( x : int , y : int ) : int = y
fun diag ( x : int ) : int * int = (x , x )
when evaluating diag(37)
is the same as diag 37
only use parentheses as necessary, but keep order and write (2 * 6) + (3 * 5)
note: type with parentheses and without are actually different type
fst diag(5)
is not the same as fst (diag 5)
(* incr : int -> int
* REQUIRES : true
* ENSURES : incr x == > the next integer after x
*)
fun incr ( x : int ) : int = x + 1
operand: input provided for function operator domain: expected input type
stdIn:4.1-4.4 Error: operator and operand don’t agree [literal]
operator domain: string * string
operand:
int * int
in expression:
3 ^ 7
variable expect string, but got int
stdIn:5.5-5.24 Error: pattern and expression in val dec don’t agree
[tycon mismatch]
pattern:
string
expression:
int
in declaration:
y : string = x * x
stdIn:1.3 Error: unbound variable or constructor: z
stdIn:1.1 Error: unbound variable or constructor: z
A function that takes a tuple and return the sum
(* add3: int * int * int -> int
* REQUIRES: true
* ENSURES: add3 (x, y, z) == > x + y + z
*)
fun add3 (t: int * int * int): int =
let
val (x, y, z) = t
in
x+y+z
end
val 0 = add3 (0, 0, 0)
val 1 = add3 (1, 0, 0)
val 3 = add3 (0, 0, 3)
A function that flips a tuple
fun flip (t: int * string): string * int =
let
val (x, s) = t
in
(s, x)
end
val (" hi ", 6) = flip (6, "hi")
A function that returns the difference
(* diff : int * int -> int
* REQUIRES : x < y
* ENSURES : diff (x , y ) == > y - x
*)
fun diff ( x : int , y : int ) : int = y - x
val 2 = diff (3 ,5)
A function that check zero
(* isZero : int -> bool
* REQUIRES : true
* ENSURES : isZero ( x ) == > true if x is 0 , and false otherwise
*)
fun isZero ( x : int ) : bool = x = 0
val false = isZero 5
fun isZero 0 = true
| isZero ( x : int ) : bool = false
val true = isZero 0
built-in orelse
function
val true = true orelse true
val true = true orelse false
val true = false orelse true
val false = false orelse false
a function that checks if one of element in tuple is zero
(* detectZeros : int * int -> bool
* REQUIRES : true
* ENSURES : detectZeros (x , y ) == > true if either x or y is zero .
*
detectZeros (x , y ) == > false otherwise
*)
fun detectZeros ( x : int , y : int ) : bool = x = 0 orelse y = 0
val true = detectZeros (0 ,3)
val true = detectZeros (3 ,0)
val false = detectZeros (3 ,3)
op ^
to show the type of ^
"a" ^ "b"
is the same as op^("a", "b")
where op
treats infix normally (see: https://piazza.com/class/kk66yt4thab5yx?cid=73)
fun times2 (x : int) : int = 2 * x
val 16 : int = times2 5 + 6
val 22 : int = times2 (5 + 6)
You can only pass 1 argument to a function
type float = real
type point = float * float
val p: point = (1.0, 2.0)
fun square (x:int) : int = x * x (* declearation *)
fn (x:int) => x * x (* expression *)
val square : int = (fn (x:int) => x * x) (* declaration *)
Function value of area (2.1 + 1.9)
is [3.14/pi] (fn (r:real) => pi * r * r)
(environment + code = closure = value of function)
Static Scope: the scope is created when function creates, and only above ("I am a professional programming language researcher, so I have opinions, and all of my opinions are correct")
val pi: real = 3.14
val area : int = (fn (r : int) => pi * r * r)
area (2.1+1.9)
(* gives 50.26 *)
val pi: int = 0
(* still gives 50.26 *)
Recursion
(* fact: int -> int
REQUIRES n >= 0
ENSURES fact n => n!
*)
fun fact (0 : int) : int = 1
| fact (n : int) : int = n * fact (n-1)
Pattern: constants(int, bool, string, char), variable(name), tuple, wildcard(__
, does not generate binding, default case)
a function can only take one type
the order of definition matters
when function domain is not exhaustive
when redundant domain
you can't bind multiple times in the same clause/pattern (like (x, x)
as a pattern or x: int, x: int
as a pattern)
val (k, r) : int * real = (2, 3.14)?????????????????????????????
(* [2/k, 3.14/r] *)
so when doing the following, its doing pattern match (no variable, don't generate bindings)
val 1 = fact 0
Case: never use if...then...else (always put parenthesis around fn
and case
)
(case e of
p1 => e1
| p2 => e2
| pn => en
)
Note that pattern matching does not generate new bindings because there is no symbol to bind to.
Table of Content