Philip Wadler (according to James Iry)
Programming paradigms
Computation is number of statements that change a program state.
PROGRAM EUCLID
PRINT *, 'A?'
READ *, NA
PRINT *, 'B?'
READ *, NB
PRINT *, 'GCD = ', GCD(NA, NB)
STOP
END
FUNCTION GCD(NA, NB)
IA = NA
IB = NB
1 IF (IB.NE.0) THEN
ITEMP = IA
IA = IB
IB = MOD(ITEMP, IB)
GOTO 1
END IF
NGCD = IA
RETURN
END
More logical program structure with concepts of modular programming and procedure calls.
// Go
func gcd(x, y int) int {
for y != 0 {
x, y = y, x%y
}
return x
}
// Rust
fn gcd(m: i32, n: i32) -> i32 {
if m == 0 {
n.abs()
} else {
gcd(n % m, m)
}
}
Programs as objects: data structures that may contain “attributes” and “methods”. Object’s methods can access and modify object state.
public interface Map<K, V> {
public Map<K, V> accept(Visitor<K, V> visitor);
}
public interface MapVisitor<K, V> {
public V visit(K k, V v);
}
/**
* Visitor that returns the GCD of k and v.
*/
public class GCD
implements MapVisitor<Integer, Integer> {
public Integer visit(Integer k, Integer v) {
if (v == 0) {
return Math.abs(k);
} else {
return visit(v, k % v);
}
}
}
Computational logic without explicit definition of control flow.
//Prolog
gcd(X, 0, X):- !.
gcd(0, X, X):- !.
gcd(X, Y, D):- X > Y, !, Z is X mod Y, gcd(Y, Z, D).
gcd(X, Y, D):- Z is Y mod X, gcd(X, Z, D).
-- SQL
SELECT customer_id, SUM (amount)
FROM payment
GROUP BY customer_id
HAVING SUM (amount) > 200;
Computation is an evaluation of functions avoiding state changing and data mutations.
gcd :: (Integral a) => a -> a -> a
gcd 0 0 = error "gcd 0 0 is undefined"
gcd x y = gcd' (abs x) (abs y) where
gcd' a 0 = a
gcd' a b = gcd' b (a `rem` b)
qsort :: (Ord a) => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort ys ++ x : qsort zs where
(ys, zs) = partition (< x) xs
Motivations: anonymity & currying
Informal definition
Reduction and recursion
Y-combinator
First-class functions are a necessity for the functional programming style, in which the use of higher-order functions is a standard practice.
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
main = map (\x -> 3 * x + 1) [1, 2, 3, 4, 5]
f :: [[Integer] -> [Integer]]
f = let a = 3
b = 1
in [ map (\x -> a * x + b)
, map (\x -> b * x + a) ]
Always evaluate the same result value given the same argument values. Also, evaluation of the result does not cause any semantically observable side effect or output.
// Pure
def sin(x: Double) = math.sin(x)
def length(s: String) = s.length()
// Impure
def inc(x: Int) = x + a
def random() = util.Random().nextDouble()
def printf(fmt: String, str: String): Unit = {
println(fmt format str)
}
// Pure, again
def random(seed: Int) = util.Random(seed).nextDouble()
def printf(fmt: String, str: String): IO[Unit] = {
for {
_ <- putStrLn(fmt format str)
} yield ()
}
Delays the evaluation of an expression until its value is needed and avoids repeated evaluations.
foldl :: (a -> a -> a) -> a -> [a] -> a
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
foldr :: (a -> a -> a) -> a -> [a] -> a
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
seq :: a -> b -> b
foldl' :: (a -> a -> a) -> a -> [a] -> a
foldl' f z [] = z
foldl' f z (x:xs) = let z' = f z x
in seq z' $ foldl' f z' xs
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
fibs :: Num a => [a]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
If you good at something, never do it for free...
qsort :: (Ord a) => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort ys ++ x : qsort zs where
(ys, zs) = partition (< x) xs
sieve :: (Num a) => [a] -> [a]
sieve (p:xs) = p : sieve [x | x <− xs, x ‘mod‘ p > 0]
primes = sieve [2..]
Why should we even care?!