Download as pdf or txt
Download as pdf or txt
You are on page 1of 3

COMP 302: Programming Languages and Paradigms

Quiz 1: Scoping, Type Checking/Inference, Evaluation

Scoping:
The in keyword declares the scope of what we just wrote. By using in, we now have access
to everything that is written before it in the lines following it. When we declare variables with
the let keyword, we use the in keyword to then have access to what we just declared in the
rest of the function.
Variables can be shadowed if they have the same name as one that was declared before.
When a variable is shadowed, there is no way to retrieve the value of the variable before the
shadowing, so when in doubt about whether you will need the original variable later on,
name them differently. Note that shadowing doesn’t change the variable associated with the
first binding of the variable.
The in keyword is notably used with nested functions, so that we have access to them in the
rest of the first level function. We can also have multiple functions after the in keyword, so
that we have access to all of them in the top-level function.

Type Checking And Inference:


A function’s types declaration is delimited by −> between types and the output type with : .
int −> int takes an integer as input and outputs an integer.
OCaml knows how to infer types depending on what we code into the function.
When we compare a parameter to an int, inference assigns the parameter to int.
OCaml also infers the return type of a function from what we assign it to do.
e.g. if n = 0 then 1 (* we compare the parameter to 0, which is int, and we return 1, which is
int, so we know that f takes an int and returns an int. *)
A function’s inputs can have different types, but each branch of the function must return the
same type.
Evaluating an expression doesn’t change its type.

A type synonym is an interchangeable name for a type, e.g. type name = string. Any
reference to string can be declared as name, and any reference to name can be declared
as string.
A tuple type is a collection of values into a bundle, e.g. person = name * height. A person
must have 2 parameters, one string for name and one int for height.
Variable/type declaration: let nick : person = (“nick”, 188) ** value nick has type person.
let nick = (“nick”, 188) ** value nick has type string * int because
we did not state the type person before assigning it.
Built-in tuple functions = fst, snd (first and second elements).
If you write a pattern, e.g. let (name, height) = nick, OCaml will match the value analyzed
against the pattern.
Output =
let name : name = “nick”
let height : height = 188

We can model types using constructors when there are multiple value possibilities.
type color = Red | Green | Yellow | Blue
type value = Numeric of int | Reverse | Skip | Plus2
type card = color * value
Recursive Types: An example of a recursive type is a List. Recursive types are well suited to
being processed by recursive functions.
Example: Natural numbers type
type nat = Z | S of nat
let five = S (S (S (S (S Z)))) (* Seen as a list: [1, 1, 1, 1, 1, NIL] *)

Polymorphic Types: A polymorphic type can be many different types. The OCaml type
inference engine assumes the types of parameters depending on how they are used.
A function’s value that is not bounded by a specific type will be noted as ‘a, ‘b, etc.
(alpha/beta).
Example: Polymorphic ID function
let id x = x ;;

List Type: Generalizing a type list to mold any type of list we want.

OCaml also has a built-in list type called ‘a list. The built-in Cons is an influx operator :: and
Nil is the empty list []. 1 :: 2 :: [] is seen as [1;2;3].
List type operations include append (@) to the end of the list.

Evaluation:
Evaluation is the process of turning expressions into values.
Expressions evaluate to values, e.g. 2+5 evaluates to value 7.
A function can evaluate multiple parameters. Parameters are usually separated by spaces,
but more complicated parameters are in parentheses ( f a b vs. f (a–1) (b+2)).
** We must declare a function recursive if we want it to evaluate to our output.
● Example: 𝑙𝑒𝑡 𝑥 = 𝑒1 𝑖𝑛 𝑒2 is an expression.
e1 evaluates to a value v. e2 evaluates with x being bound to v. This means that
occurrences of x in e2 are replaced by v. The overall result of the expression is
whatever e2 evaluates to.

A function in OCaml can only “see” what is declared above it. If you declare a variable before
a function, declare other variables of the same name, and call the function later on with the
variable, it will evaluate with the variable above it, because it cannot “see” what is after it.
If we want a function only to evaluate one parameter, but we need two parameters to run it,
we can build an inner helper function to handle the two parameters (as is the case with
factorials, sums, primality check, etc.)
let is_prime n =
let rec go (i : int) : bool =
if i = n then true else
if n mod i = 0 then false else
go (i+1)
in
go 2
Recursiveness: Normal recursiveness takes up a lot of memory. When tracing, operations
are done “on the way back” instead of “on the way there”. Tail recursion can recycle the
same allocated memory space (stack frame) if we perform our operations “on the way there”.
Tail recursion is defined as a recursive function in which the recursive call is the last
statement that is executed by the function. Nothing is left to execute after the recursion call.
Example 1: Tail-recursive sum function

Example 2: Tail-recursive factorial function

let factorial' n =
let rec go n partial_prod =
if n = 0 then partial_prod else
factorial' (n-1) (partial_prod*n)
in
go n 1

You might also like