Haskell, Scala, F#Are they worth the fuss?
My Background• B.Tech. IIT-Bombay. Computer Science.
• Ph.D. USC. Algorithmic Algebra.
• VP. Goldman Sachs. Trading Strategist.
• Managing Director. Morgan Stanley.
• Founder & C.E.O. ZLemma (A Tech Startup).
Haskell, Scala, F#• A different way of thinking about coding
• Functional Programming is just one feature
• High productivity (not easy to produce bugs)
• Elegant and expressive (readable, maintainable)
• Enjoyable and Addictive!
A different way of thinking
• The way we think when we do Math
• It’s not about objects, state and “recipes”
• It’s about (in Math-speak) sets, functions, compositions
• Express what you want to do, not “how to do”
• Elite programmers are influencing a cultural shift
More than just FP
• FP means stateless code & higher-order functions
• {Haskell, Scala, F#} -> many other nice things
• Static but Inferred Typing. Hence, safe yet succinct
• Algebraic Data Types & Pattern Matching
• The M word …
Stateless code• Everything is a const. Banish the word “variable”
• So: no loops, no reassignments, no object edits
• Instead: map, fold, recursion, algebraic data types
• Many small functions delegating to other functions
• “State is the root cause of bugs” #MadeUpQuote
def isprime(n): if n == 2: return True if n == 1 or n % 2 == 0: return False max = n**0.5+1 i = 3 while i <= max: if n % i == 0: return False i+=2 return 1
Python done badly
def isPrime(n): if n == 1: return False else: return all(n % i != 0 for i in range(2, int(sqrt(n)) + 1) if isPrime(i))
Python done well
But what about typing?
let notFactorOf n = fun i -> n % i <> 0 let intSqrt n = (int << sqrt << float) nlet rec isPrime = function | 1 -> false | n -> Seq.forall (notFactorOf n) (seq {for i in 2 .. (intSqrt n) do if isPrime i then yield i})
Same code in F#
Higher-order Functions• Think of functions as a “generalization of data”
• Functions accepting and/or returning function
• Function composition (chaining)
• Exploit the power of lambdas and currying
• Laziness and memoization
class NumSet(object):
def __init__(self): self.__data__ = None
def __calc__(self): return self
def get_calc(self): if not self.__data__: self.__data__ = self.__calc__() return self.__data__
class NumSetLeaf(NumSet): def __init__(self, low, mid, high): super(NumSetLeaf, self).__init__() self.low = low self.mid = mid self.high = high self.triple = (self.low, self.mid, self.high)
class NumSetOp(NumSet): def __init__(self, fl, args): super(NumSetOp, self).__init__() self.fl = fl self.args = args
def __calc__(self): lows, mids, highs = zip(*[x.get_calc().triple for x in self.args]) fl = self.fl return NumSetLeaf(fl(lows), fl(mids), fl(highs))
class NumSetSum(NumSetOp): def __init__(self, args): super(NumSetSum, self).__init__(sum, args)
Can Python “Haskell”?
class NumSetUnaryOp(NumSetOp): def __init__(self, f1, arg): super(NumSetUnaryOp, self).__init__(lambda l1, f1=f1: f1(*l1), [arg]) self.f1 = f1 self.arg = arg
class NumSetScale(NumSetUnaryOp): def __init__(self, arg, k): super(NumSetScale, self).__init__(lambda x, k=k: x * k, arg) self.k = k
class NumSetBinaryOp(NumSetOp): def __init__(self, f2, arg1, arg2): super(NumSetBinaryOp, self).__init__(lambda l2, f2=f2: f2(*l2), [arg1, arg2]) self.f2 = f2 self.arg1 = arg1 self.arg2 = arg2
class NumSetPlus(NumSetBinaryOp): def __init__(self, arg1, arg2): super(NumSetPlus, self).__init__(lambda x, y: x + y, arg1, arg2)
class NumSetMult(NumSetBinaryOp): def __init__(self, arg1, arg2): super(NumSetMult, self).__init__(lambda x, y: x * y, arg1, arg2)
l1 = NumSetLeaf(0.9, 0.92, 0.95 l2 = NumSetLeaf(0.8, 0.85, 0.9) l3 = NumSetLeaf(0.6, 0.7, 0.8) k = 0.6 l4 = NumSetPlus(NumSetScale(l1, k), NumSetScale(l2, 1.0 - k)) l5 = NumSetScale(NumSetSum([l4, l2, l3]), 0.3)
Can Python “Haskell”?
Infered Typing• The best of both worlds
The rigor and safety of static typing
Type inference lends syntactic lightness
• Most coding errors are caught as one types code
• Visualization of typing makes one think better
• Scripting-style, rapid prototyping, REPL
Algebraic Data Types• Elegant (recursive) way to express data structures
• Makes writing of algorithms very easy and natural
• Pattern-matching on edge cases and types
• No more ugly/dangerous if-elseif, switch/case code
• Type/structural safety. Hard to introduce bugs.
type color = R | B type 'a tree = | E | T of color * 'a tree * 'a * 'a tree module Tree = let hd = function | E -> failwith "empty" | T(c, l, x, r) -> x let left = function | E -> failwith "empty" | T(c, l, x, r) -> l let right = function | E -> failwith "empty" | T(c, l, x, r) -> r let rec exists item = function | E -> false | T(c, l, x, r) -> if item = x then true elif item < x then exists item l else exists item r let balance = function (* Red nodes in relation to black root *) | B, T(R, T(R, a, x, b), y, c), z, d (* Left, left *) | B, T(R, a, x, T(R, b, y, c)), z, d (* Left, right *) | B, a, x, T(R, T(R, b, y, c), z, d) (* Right, left *) | B, a, x, T(R, b, y, T(R, c, z, d)) (* Right, right *) -> T(R, T(B, a, x, b), y, T(B, c, z, d)) | c, l, x, r -> T(c, l, x, r) let insert item tree = let rec ins = function | E -> T(R, E, item, E) | T(c, a, y, b) as node -> if item = y then node elif item < y then balance(c, ins a, y, b) else balance(c, a, y, ins b) match ins tree with | E -> failwith "Should never return empty from an insert" | T(_, l, x, r) -> T(B, l, x, r)
Red-Black Tree in F#
Make Data, Not Code• Data-driven coding (parametric algorithms)
• Structured data framework controls your algorithms
• A system where key logic is in data, not code
• User wants a lot of control => Give him a DSL
• Plug & Play system => Bring Your Own Data (DSL)
DSL• Internal versus External
• External: End-User DSL versus Dev DSL
• Internal: Syntactic Sugar for clean, controlled code
• Graphical representation of DSL => Functional
• Well-typed, Functional DSL => Happy Algorithms!
The M Word …• Monads - not just for “side-effects computing”
• Functional way of doing imperative/sequential logic
• Define Domains, Ranges, Compositions (Chains)
• Sounds complex but yields clean, bug-free code
• 4 types of X => M(X) monadic “Range Expansion”
• g: A -> M(B), f: B -> M(C). How to get f o g: A -> M(C)
4 types of Monads• Failure: A -> Maybe(B), B -> Maybe(C).
• Configurations: A -> Settings(B), B -> Settings(C).
• Uncertainty: A -> Power(B), B -> Power(C).
• Side-Effects: A -> IO(B), B -> IO(C)
• Just figure out the bind and unit methods for each
• Chain with bind and unit, and it’s all automatic!
type Maybe<‘a> = | Just of 'a | Nothing
let Return (x: 'a) : Maybe<'a> = Just(x)
let Bind (mx: Maybe<'b>) (f: 'b -> Maybe<'c>) : Maybe<'c> = match mx with | Just(x) -> f x | Nothing -> Nothing
Failure: “Maybe” monad
Maybe(X) is an algebraic data type that can be X or “Error”
g: A -> Maybe(B) f: B -> Maybe(C)
How to produce f o g : A -> Maybe(C)
let Return (x: 'a) : (‘s -> ‘a) = fun _ -> x
let Bind (mx: ’s -> ‘b) (f: 'b -> (’s -> ‘c)) : (’s -> ‘c) = fun y -> f (mx y) y
Configurations: “Settings” monad
Settings(X) is a function from a “Settings” type to X
g: A -> Settings(B) f: B -> Settings(C)
How to produce f o g : A -> Settings(C)
let Return (x: 'a) : Set<‘a> = set [x]
let Bind (mx: Set<‘b>) (f: 'b -> Set<‘c>) : Set<‘c> = set [for x in mx do yield! f x]
Uncertainty: “Power” monad
Power(X) is the power set of X (the set of all subsets of X)
g: A -> Power(B) f: B -> Power(C)
How to produce f o g : A -> Power(C)
let Return (x: 'a) : (unit -> ‘a) = fun () -> x
let Bind (mx: unit -> ‘b) (f: 'b -> unit -> ‘c) : (unit -> ‘c) = f (mx())
Side-effects: “IO” monad
IO(X) is a side-effects-function from Null (i.e., no args) to X
g: A -> IO(B) f: B -> IO(C)
How to produce f o g : A -> IO(C)