Monday, November 14, 2011

Postcast notes for Simon Peyton Jones Interview on Software Engineering Radio

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