My notes for: Episode 108: Simon Peyton Jones on Functional Programming and Haskell
Got hooked on FP's elegance as undergrad
Turn Haskell from idea into real world
Late 80’s
friends working in diff research labs on non-strict/lazy languages – we should
have one language. Haskell was born
Purity
·
E.g. Spreadsheeet
·
Formulae – compute value of cell based on value
of other cells
·
Wouldn’t write “= print something, 3” – don’t
know when it will be executed
·
Formulae simply compute values from other values
– doesn’t make sense to do anything else
Imperative =
Location-based, values of mutable locations change, x not value but location
Functional =
value-based, x is a value, same value anywhere it’s in scope
Can you
write any useful programs at all in such an emasculated language?
Lambda
Calculus
·
is to functional programming as x86 is to
imperative
·
most boiled down version of FP you can imagine
·
tiny language with only 3 constructs: variables,
lambda abstractions and applications
·
3 syntactic forms and one reduction rule
·
Can translate monstrously complicated Haskell
programs into this tiny calculus
·
Can watch tiny calculus executing
·
Execution mechanism for a functional program –
doesn’t happen like that but good way to think about it
·
Essence of FP in an incredibly small space
Applying
function to values
What
kind of args can function take?
·
Higher-order functions
·
delegates
·
Currying
f a b =
“f applied to applied to b”
·
It’s how you think about it
·
Actual impl some analysis to say this function
can’t do any work until it’s given 3 args, we’ll implement it as machine code
that demands 3 args if given fewer we’ll save them up somewhere and arrange to
give it to them later.
Side-effects
·
when procedure does something (anything) other
than transform its args into a new result value
·
e.g. write into persistent mutable state, take
input, delete files, launch missiles
·
void
·
Why bad?
·
Why side-effect free good?
·
Easier to understand and reason about. Less
happening behind the scenes
·
Easier to maintain when I make a change to
function that changes interface/type.
Compiler tells me lots of other places I need to change. If still has int -> void but instead
modifies different global variables then it’s harder to see consequences
·
Testing is different –
·
imperative: set up state of world to be ready,
call it, inspect state of world and see if expected state has changed as
expected. Can work around it with mocks
but uphill struggle.
·
Functional: simply give test values as input and
look at output. No state to setup. No old/new.
·
Leads to better automation – e.g. QuickCheck
·
Thread-safe
·
Imperative: Can’t put hand on heart and say it’s
thread-safe
·
Functional: difficult to get wrong
·
Not true to say you can just push a button on a
FP and it will run in parallel
·
Data dependencies might sequentialise it. Data
flow.
·
May be difficult to get a sufficiently large level
of granularity to execute efficiently
·
Recursion
·
replaces iteration
·
tail recursion.
·
Must do to be able to claim to be able to
execute arbitrary programs with same asymptotic efficiencies
·
If function calls itself (or any other function)
and it’s last thing it does, then instead of calling it then removing stack
frame, remove stack frame right away… don’t grow stack.
·
.NET/JVM don’t support tail recursion very
well. .NET does reasonably well (FP guys
involved)
·
lazy evaluation (memoisation)
·
when u call function you don’t evaluate args
before call instead you pass to the function suspension/thunk/recipe/closure
(wrap up the arg) then if the function ever needs the value of that arg it
pokes on it and that causes it to then do the computation (delayed) laziness/call-by-need/call-by-name.
·
Call-by-value is normal way that LISP / C# does
it.
·
Important for if statement you only want to
evaluate the if or the then part
·
Lazy language can implement if as function (not
as language construct)
·
One of Haskell’s distinctive features and reason
it’s stayed pure.
·
If lazy language, caller of function doesn’t
know when it’s evaluated. If
side-effects, caller doesn’t know when or in what order it’s called. Much less reasonable to say we’ll allow you
to do side-effects inside these functions.
·
Important thing is purity – laziness has helped
us to stay pure – laziness has helped us to stay pure – high order bit is
purity.
·
Memoisation
·
Cache the result
·
Expensive to apply to every function all the
time
·
Would be nice to be able to say memoise this
·
Might want to do a static transformation (at
compile-time) transform inefficient to efficient (dynamic programming) that is
justified by side-effect-free nature of calls
Data
types
·
Define new data types all the time
·
Algebraic data types
·
Data type T :: A Int Float Bool | B | C Float
·
A/B/C constructors
·
E.g. T abstract class with 3 final subclasses
A/B/C
·
Can build values of type T using constructions
·
Define functions by cases
·
Erlang don’t declare type just do pattern
matching free-wheeling
·
Helpful to say this f takes value of type T, T
has exactly 3 cases A, B & C done.
At runtime I’m never going to get a value I don’t recognise.
·
Datatypes very important
·
Can’t add new constructors later (Scala can).
·
Functions and datatypes play nicely together
·
Datatype is passive
·
OOP class give behaviour
·
FP behaviour – easy to add new types with same
operations, but adding new operation is difficult – tradeoff.
·
Easy to add new functions over that type but not
new variants of the datatype [because it affects many functions]
·
Scala has best approach to trade-off
Side-effects
·
Haskell very embarrassed couldn’t do much no
side effects whole program String -> String
·
Led us to Monads
·
Phil Wadler early 90s converted Amogie’s paper
to program. Simon said we could use this
to do imperative FP (side-effects) inside Haskell program without polluting whole
language with side-effects every where.
·
Paper “Imperative Functional Programming”
·
Side-effect bits and pure bits are kept apart by
type system
·
String -> IO String e.g. List<String> type
constructor like generic
·
Give me string I’ll give you IO
computation. Can do some IO and then
return a value of given type.
·
Because it’s part of type system the compiler
sees IO and doesn’t apply reordering and other (lazy?) optimisations because it
would be dangerous here.
·
Can inline (String -> IO String) move it
around, store value of IO T in a data structure, or pass as arg or return as
result
·
Compiler: IO T looks like function World -> (A,
World). Computations represented as function.
Haskell-centric view of the world IO Computation takes in entire
universe and transforms universe by mutating one variable in it and returns new
slightly modified universe.
·
IO stuff is simply transformed into more lambdas
then compiler optimises lambdas like crazy (just like any other lambdas) 90% of
the compiler doesn’t know any funny business.
Monads
·
A way of classifying side-effects
·
Design pattern IO is one instance, other
instances e.g. State Transformer Monad, Exception Monad
·
Type classes are way of describing computation
pattern / design pattern
·
Monad class (not OOP) (class of types) (design
pattern) allows anyone to build new Monad that’s supported syntactically by
compiler by do notation
Transactional
Memory
·
Dan Grossman Garbage Collection and
Transactional Memory
·
Relationship between Monads and Transactional
Memory
·
Revise TM:
·
Want to say atomically do this, this, this where
these things are mutations of transactional variables.
·
Not allowed to say atomically do this, this,
this and launch the missiles – because transaction is meant to be executed
optimistically and then rollback and try again so it’s important it doesn’t
make any commitments like destroying small countries before that it knows it’s
ready to commit.
·
Helpful to be able to classify the side-effects:
this block of code/expression does only side-effects on transacted variables
·
In STM Haskell the Atomic construct is not a
language construct just a function type is STM a -> IO a
·
Takes as arg a computation that does STM-like
things (side-effecting computation but one that only manipulate STM variables) and
it runs it atomically and delivers the result into the IO Monad which is
richer. Stratification of effects STM limited, IO wider. Inside the STM monad you may be calling pure
functions that have no effect at all.
Build a
vocabulary of functions that match your application – DSL
“Haskell
DSL” in google
Parsers
YACC / LEX take different language and transform into C
Parser
a
Combinator
(Parser a) (Parser b) -> (a, b)
many
Parser a -> Parser [a]
infix
functions
Yacc
grammer embedded DSL written in Haskell instead of Yacc with Haskell’s benefit
of modules to scale up.
Banks,
web, machine-learning, gene-sequencing.
Algorithm
heavy
Complicated
algorithm with rich data structures
Not suited
to take giant blob of XML do something meaningless/modest – need libraries.
Haskell
has hackage - 100 new libraries per month.
No comments:
Post a Comment