Lecture 002

Types

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)

Declaration

val Declarations

identifiers: variable names

val <varname> : <type> = <expr>

Evaluation

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 Declarations

Scope

val 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.

Shadowing

If you bind to the same variable twice, the first binding exist prior to the second binding. See code example from Lecture_001

Hierarchy of Text in SML

Hierarchy of Text in SML

You can think of expressions as "something eventually will be a value" and declarations as "commands" or "actions" for the computer to do work.

Types

infix operator: a function that can be insert between two arguments

You can use infix 9 ^ to declear infix operator ^ where 9 denotes a priority of evaluation.

String

^: string concat

Binding

binding: each use of val is one binding

declaration:

val expression

Without 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 expression

let val < varname > = < expr1 > in < expr2 > end

Immutability

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

Tuples

val ( x : int , y : int ) = (3 ,4)
val z : int * int = (3 ,4)

Writing Tests

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.

Functions

Importing from Files

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
-

Applying Functions

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

Function Specifications (function spec)

(* incr : int -> int
* REQUIRES : true
* ENSURES : incr x == > the next integer after x
*)
fun incr ( x : int ) : int = x + 1

Error Message

Operator's Type Error

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

Binding Type Unmatch

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

Unbounded Variable

stdIn:1.3 Error: unbound variable or constructor: z
stdIn:1.1 Error: unbound variable or constructor: z

Sample Functions

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)

Tricks

Type Checking

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)

Parenthesis

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 Binding

type float = real
type point = float * float
val p: point = (1.0, 2.0)

Functions

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)

Doing Calculation as Math

Doing Calculation as Math

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)

Fast Fib

Fast Fib

Pattern: constants(int, bool, string, char), variable(name), tuple, wildcard(__, does not generate binding, default case)

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
)

Example Case

Example Case

Note that pattern matching does not generate new bindings because there is no symbol to bind to.

Example function as value

Example function as value

Table of Content