We are used to imperative programming.
Recall — Imperative Programming:
- Programs are structured as a sequence of instructions.
- Uses destructive updates for computations.
Remember — All programs transform data into something else.
Functional Programming:
Example — You can’t do x = 1 + y, instead you have to use functions.
Definition — Mathematical Function: A special type of relation.
- Relation: A subset of the cross product of two sets.
Related Notes — CS1300: Relations
tl;dr we are creating pairs, where the order matters.
If you cross a set with itself ???, you can define special properties like (e.g., int cross int, take a subset (relation)):
e.g., prof. is his daughter’s Dad (symmetric), prof. is his wife’s spouse and his wife is his spouse (reflexive),
To add: Injective is another word for one-to-one. Surjectivity is when ???. If an ?? is both injective and surjective, we have a bijective relation. Bijectives have inverses, and the inverses are also functions.
Function are injective. They can be one-to-one or many-to-one.
This is only a function if you provide domain and codomain.
f(x) = x + 1
Z \to Z
Z: Set of all integers.
Notice how this doesn’t change anybody. It keeps everything the same.
A program, rather than being a list, now just looks like
f_n ( f(_{n-1} ( f_{n-2} ( ... f_2 ( f_1 (\text{input} ) ) ) )
There are no variables. Functional programming languages like Prolog let you use let, which simply binds symbols to value, which cannot change. Variables can change, and functional languages don’t support them.
You can technically program functionally in Java, but it’s not designed for it.
The root of all evil: Destructive updates.
If there are no changes, all computation is local.
There are parts of a program that need to do destructive updates.
So, most functional languages provide some imperative features like printing to the screen that give you arrays, printing, etc.
TODO What is a function? What arhe relations?
Now, we’ll talk about functional programming in the context of SML for some concrete examples.
SML: A functional language.
Etymology — Stands for Standard ML, because it’s a modern version of ML (Meta-Language) a functional lang. made by Robin Milner in 1973. ML was made to talk about languages.
- One of the few fully formal language specifications (it’s literally all math).
There are several industrial-strength compilers.
“If your task is to write a compiler, bam. SML. It’s delicious.” professor then recommends “Practical Compilers in SML” for the third time. He has done like one for every language, but his SML one is “the best”
Functions are first-class citizens.
Everything is built from expressions.
Powerful static type system:
Advanced module system.
TODO Exception handling and Garbage collection. Suppose you type in Now suppose you then do In other words, because we can create garbage in SML, we need a garbage collector. TODO What is int? It’s some sort of binding that gets GCed?Example: Memory Managment
val x = 1; into the interactive prompt.val declares a new symbol.M' for this, which can never change.val x = 3;, what happened?
There are four general purpose types: Integers (int), floating points (real), strings (string), booleans (biol), and characters (char).
Arithmetic Operators:
+, -, *, div+, -, *, /Why div? — We save
/for reals because integer and floating-point division are different operations.
- Don’t mismatch types!
Unary Negation: ~
TODO Unary negation is different from subtraction???
TODO 47 - 5; versus ~5 + 47 ???
TODO So to make negative numbers, don’t use the subtraction operator, use unary.
Logical Operators:
>, >=, <, <=, =, <>Example: TODO
String Concatenation: ^Example: String Concatenation
"Hello" ^ " World"
Bindings between names and values.
Don’t forget, they’re NOT assignments!
Functions are defined using the fun keyword.Example: Functions
- fun f(x) = x+42;
val f = fn : int -> intint -> int is telling us this maps integers to integers
Functions are pure (they have no side-effects), aka: it doesn’t modify any memory.
Lambda functions are created with TODO this is mighty incomplete.fn and =>Example: TODO I don’t get this dawg.
x => x * x
You can declare multiple parameters as a tuple or in Curried form.
Definition — Curried Form: TODO
Tuple:
fun add(x,y) = x + y;
val add = fn : int * int -> intCurried Form:
- fun add x y = x + y;
val add = fn : int -> int -> int
- add 3 4;
---
**Using Curried Form**:
```sml
- val plusFour = add 4;
val plusFour - fn : int -> int
- plusFour 3;
val it = 7 : intfun fac(x) = if (x=0) then 1 else x * fac(x-1);Iteration v. Recursion — They both perform an action over-and-over. The difference is, in recursion, you repeat by calling the same function again; while in iteration you repeatedly jump until a condition is true.
The short of it: Recursion consumes more memory, but is easier to reason about.
Definition — Polymorphism: The ability of an element to appear to have multiple forms or behaviors depending on context.
If the type engine can’t determine a type, the name gets a polymorphic type. This is very different from taking an object in Java. This is more similar to templates in C++.Example: Polymorphic Function
- fun toList x = [x];
val toList = fn : 'a -> 'a list
- toList 42;
val it = [42] : int list
- toList "hello";
val it = ["hello"] : string list
Values from cross-products of different sets.
Example: TODO use # Sequences of value of the same type. Operations Aside — SML does have arrays, but you want to avoid them as much as possible. Example: TODO do an example of all of these. I also don’t fully understand head and tail. SML features a powerful pattern-matching mechanism. Example: TODO Fibonacci example <!–
take a list of integers and compute their sum fun sum() Base Case: Empty list -> 0
fun sum nil = 0 Inductive Case: Add head to “rest of list”
| sum l = hd(l) + sum (tl(l)); Pro: fun sumV2 nil = 0
| sum(x::xs) = x + sum xs Can’t see the projector. He does something with pattern matching and skips using the tail and head functions. This style aids a lot with more complex code. Q: Instead of the sum, compute th length of the list A: fun len nil = 0
| len x = 1 + len(tl(x)); Q: Now, return the reverse of the input of the list A: fun rev nil = nil
| rev (x::xs) = (rev xs)@[x]; (x::xs) We reverse the rest of the list, and append the front example trace
suppose it it called on [1,2,3] x will be bound to 1
xs will be bound to [2,3] recursive x will be bound to 2
xs will be bound to 3 recursive x will be bound to 3
xs will be bound to [] recursive base case hit, begin returning upwards why does this program suck? because of the @, which is linear (this makes the algorithm quadratic). Additionally, the stack of activiation records will explode. So, here is a version that will tackle both problems. tailrev takes a list and an accumulator. we’ll use it. A good SML programmer always programs like this, and never the first way! How does this work? We have our list, and all we’re doing is taking the first element and putting it in the accumulator, until nothing is left. We are doing Tail Recursion, the last action is takes in the inductive case is the recursive call. Related Note: Recursion (CS2400)
–> GOAL: Write a function that computes the sum of a list, using Tail recursion fun tailadd [] acc = acc
| tailadd (x::xs) acc = tailadd xs (x+acc); fun wrapadd l = tailadd l 0; Stack overflow is when the stack collides with the heap. To avoid stack overflow, do tail recursion so that the stack doesn’t grow. Recall — Recall that memory is organized into four sections: Definition — Activation Record: Stores everything you need for a function (local variables, arguments, etc.) We can create local bindings to simplify function definitions. Functions can be passed to other functions easily. Notice how we did not do anything special to pass the function. Because we use f like a function, SML knows it must be a function. Example: Q: Write a version of the apply function, that is tail recursive : A:Lists
[] or nil::: Prepend value (constant time)@: Concatenate two lists (linear time)hd: Return headtl: Return tailPattern Matching
- fun fact 0 = 1
= | fact x = x * fact(x-1);
val fact = fn : int -> intfun tailrev [] acc = acc
| tailrev (x::xs) acc = tailrev xs (x::acc);fun brev l = tailrev l [];Avoiding Stack Overflow
Let Expressions
TODO
let
value h1
value h2
in
(h1,h2)Higher-Order Functions
fun plusOne x = x+1
fun apply f [] = []
| apply f (x::xs) = f(x)::(apply f xs);
apply plusOne [41,41,41]fun plusOne x = x+1;
fun tailapply f [] acc = acc
| tailapply f (x::xs) acc = tailapply f xs ((f(x))::acc);
fun wrapapply f l = rev (tailapply f l [] );