33 - Functional#
ML (Meta Language)#
A static-scoped language with syntax that is closer to Pascal than to Lisp
It is strongly typed (whereas Scheme is essentially typeless) and has no type coercions
Uses type declarations, but also does type inferencing to determine the types of undeclared variables
Does not have imperative-style variables; its identifiers are untyped names for values
Includes lists and list operations
A table called the evaluation environment stores the names of all identifiers in a program, along with their types (like a run-time symbol table)
Function declaration form:
fun name(formal parameters) = expression;
fun cube(x : int) = x * x * x;
The type could be attached to a return value, as in
fun cube(x) : int = x * x * x;
With no type specified, it would default to
int
(the default for numeric values)User-defined overloaded functions are not allowed, so if we wanted a
cube
function for real parameters, it would need to have a different name
Selection#
if expression then then_expression
else else_expression
where the first expression must evaluate to a Boolean value.
Pattern matching is used to allow a function to operate on different parameter forms:
fun fact(0) = 1
| fact(1) = 1
| fact(n : int) : int = n * fact(n - 1)
Lists#
Literal lists are specified in brackets: [3, 5, 7]
.
[]
is the empty listCONS
is the binary prefix operator,::
4 :: [3, 5, 7]
, which evaluates to[4, 3, 5, 7]
CAR
is unary operatorhd
CDR
is the unary operatortl
fun length([]) = 0
| length(h :: t) = 1 + length(t);
fun append([], lis2) = lis2
| append(H :: t, lis2) = h :: append(t, lis2);
The val
Statement#
The val
statement binds a name to a value (similar to DEFINE
in Scheme).
val distance = time * speed;
As is the case with
DEFINE
,val
is nothing like an assignment statement in an imperative languageIf there are two
val
statements for the same identifier, the first is hidden by the secondval
statements are often used inlet
constructs
let
val radius = 2.7
val pi = 3.14159
in
pi * radius * radius
end;
filter
#
A higher-order filtering function for lists
Takes a predicate function as its parameter, often in the form of a lambda expression
Lambda expressions are defined like functions, except with the reserved word
fn
filter(fn(x) => x < 100, [25, 1, 711, 50, 100]);
This returns [25, 1, 50]
.
map
#
A higher-order function that takes a single parameter, a function
Applies the parameter function to each element of a list and returns a list of results
fun cube x = x * x * x;
val cubeList = map cube;
val newList = cubeList [1, 3, 5];
This sets newList
to [1, 27, 125]
.
Alternatively, you can use a lambda expression
val newList = map(fn x => x * x * x, [1, 3, 5]);
Function Composition#
Use the binary operator, o
val h = g o f;
Haskell#
Similar to ML (syntax, static scoped, strongly typed, type inferencing, pattern matching)
Different from ML (and most other functional languages) in that it is purely functional (e.g. no variables, no assignment statements, and no side effects of any kind)
Syntax differences from ML:
fact 0 = 1
fact 1 = 1
fact n = n * fact(n - 1)
fib 0 = 1
fib 1 = 1
fib(n + 2) = fib(n + 1) + fib n
Function Definitions with Different Parameter Ranges#
fact n
| n == 0 = 1
| n == 1 = 1
| n > 0 = n * fact(n - 1)
sub n
| n < 10 = 0
| n > 100 = 2
| otherwise = 1
square x = x * x
Because Haskell supports polymorphism, this works for any numeric type of
x
Lists#
List notation: Put elements in brackets
[]
e.g.
directions = ["north", "south", "east", "west]
Length:
#
e.g.
#directions
is4
Arithmetic series with the
..
operatore.g.
[2, 4..10]
is[2, 4, 6, 8, 10]
Catenation is with
++
e.g.
[1, 3] ++ [5, 7]
results in[1, 3, 5, 7]
head, tail, :, for CAR, CDR, CONS
e.g.
1:[3, 5, 7]
results in[1, 3, 5, 7]
Pattern Parameters#
product[] = 1
product(a:x) = a * product x
Factorial:
fact n = product [1..n]
List Comprehensions#
[n * n * n | n <- [1..50]]
The qualifier in this example has the form of a generator. It could be in the form of a test
factors n = [i | i <- [1..n `div` 2], n `mod` i == 0]
The backticks specify the function is used as a binary operator.
Quicksort#
sort [] = []
sort (h:t) =
sort [b | b <- t, b <= h]
++ [h] ++
sort [b | b <- t, b > h]
Lazy Evaluation#
A language is strict if it requires all actual parameters to be fully evaluated.
A language is nonstrict if it does not have the strict requirement
Nonstrict languages are more efficient and allow some interesting capabilities - infinite lists
Lazy evaluation - Only compute those values that are necessary
Positive numbers:
positives = [0..]
Ex. Determining if 16
is a square number
member [] b = False
member(a:x) b=(a == b) || member x b
squares = [n * n | n <- [0..]]
member squares 16
Member Revisited#
The member function could be written as:
member b [] = False
member b (a:x) = (a == b) || member b x
However, this would only work if the parameter to squares was a perfect square; if not, it will keep generating them forever. The following version will always work:
member2 n (m:x)
| m < n = member2 n x
| m == n = True
| otherwise = False
F##
Based on Ocaml, which is a descendent of ML and Haskell
Fundamentally a functional language, but with imperative features and OOP support
Has a full-featured IDE, an extensive library of utilities, and interoperates with other .NET languages
Includes tuples, lists, discriminated unions, records, and both mutable and immutable arrays
Supports generic sequences, whose values can be created with generators and through iteration
Sequences#
let x = seq {1..4};;
Generation of sequence values is lazy
let y = seq {0..10000000};;
Sets
y
to[0; 1; 2; 3; ...]
Default stepsize is
1
, but it can be any numberlet seq1 = seq {1..2..7};;
Sets
seq1
to[1; 3; 5; 7]
Iterators to create sequences
let cubes = seq {for i in 1..4 -> (i, i * i * i)};;
Sets cubes to
[(1, 1); (2, 8); (3, 27); (4, 64)]
Functions#
If named, defined with
let
; if lambda expressions, defined withfun
let add a b = a + b;;
(fun a b -> a + b)
No difference between a name defined with
let
and a function without parametersThe extent of a function is defined by indentation
let f =
let pi = 3.14159
let twoPi = 2.0 * pi
twoPi;;
If a function is recursive, its definition must include the rec
reserved word
let rec factorial n =
if n <= 1 then 1
else n * factorial(n - 1)
Names in functions can be outscoped, which ends their scope.
let x4 x =
let x = x * x
let x = x * x
x;;
The first let
in the body of the function creates a new version of x
; this terminates the scope of the parameter. The second let
in the body creates another x
, terminating the scope of the second x
.
Functional Operators#
The pipeline (|>
)
A binary operator that sends the value of its left operand to the last parameter of the call (the right operand)
let myNums = [1; 2; 3; 4; 5] let evensTimesFive = myNums |> List.filter(fun n -> n % 2 = 0) |> List.map(fun n -> 5 * n)
The return value is
[10; 20]
Composition (>>
)
Builds a function that applies its left operand to a given parameter (a function) and then passes the result returned from the function to its right operand (another function)
The F# expression
(f >> g) x
is equivalent to the mathematical expression \(g(f(x))\)
Why F# Is Interesting#
It builds on previous functional languages
It supports virtually all programming methodologies in use today
It is the first functional language that is designed for interoperability with other widely used languages
At its release, it had an elaborate and well-developed IDE and libary of utility software