Functional Programming and Haskell
Erik Charlebois
Slant Six Games
March 5th, 2009
Outline
Functional Programming Core Concepts
Haskell Haskell Language Haskell Features
Real World Functional Programming
Functional Programming
Higher Order Functions
Functions can take functions as arguments and return them as results.
Higher Order Functions
Functions can take functions as arguments and return them as results.
> :type map map :: (a -> b) -> [a] -> [b] > let add1 x = x + 1 > map add1 [1,2,3] [2,3,4]
Higher Order Functions
Functions can take functions as arguments and return them as results.
> :type map map :: (a -> b) -> [a] -> [b] > let add1 x = x + 1 > map add1 [1,2,3] [2,3,4]
> let addn n = \x -> x + n > map (addn 2) [1,2,3] [3,4,5]
Purity
Functions take some input and generate some output with no observable side effects.
Purity
Functions take some input and generate some output with no observable side effects.
// Oops... int add(int x, int y) { launch_missiles(); return x + y; }
Purity
Functions take some input and generate some output with no observable side effects.
// Oops... int add(int x, int y) { launch_missiles(); return x + y; }
-- Compile error! let add x y = case launch_missiles of _ -> x + y
Recursion
Functions invoke themselves to perform iteration.
Recursion
Functions invoke themselves to perform iteration.
-- Inductive definition for map map fn [] = [] map fn (x:xs) = (fn x) : (map fn xs)
Recursion
Functions invoke themselves to perform iteration.
-- Inductive definition for map map fn [] = [] map fn (x:xs) = (fn x) : (map fn xs)
-- Inductive definition for left fold foldl :: (a -> b -> a) -> a -> [b] -> a foldl fn a [] = a foldl fn a (x:xs) = foldl fn (fn a x) xs sum ls = foldl (+) 0 ls product ls = foldl (*) 1 ls
Strictness Arguments can be evaluated prior to a call (eager) or as needed (lazy).
Strictness Arguments can be evaluated prior to a call (eager) or as needed (lazy).
-- Custom control flow constructs. myif True tp _ = tp myif False _ fp = fp
Strictness Arguments can be evaluated prior to a call (eager) or as needed (lazy).
-- Custom control flow constructs. myif True tp _ = tp myif False _ fp = fp
-- Out of order definition. let x = y + 1; y = 3 in x
Strictness Arguments can be evaluated prior to a call (eager) or as needed (lazy).
-- Custom control flow constructs. myif True tp _ = tp myif False _ fp = fp
-- Out of order definition. let x = y + 1; y = 3 in x
-- Cyclic data structures. > let x = 1 : 2 : 3 : x > x [1,2,3,1,2,3,1,2,3,1,2,3,1,...] > take 10 x [1,2,3,1,2,3,1,2,3,1]
Haskell
Haskell
Inspired by Miranda Originally a platform for programming language research Standardized in 1998 as Haskell 98 Development of primary compiler funded by Microsoft Research
Haskell
Inspired by Miranda Originally a platform for programming language research Standardized in 1998 as Haskell 98 Development of primary compiler funded by Microsoft Research Pure, lazy functional programming language Garbage-collected Statically-typed Compiled or interpreted
Currying
A function can be called without all of its arguments. A new function is returned which takes the remaining arguments.
Currying
A function can be called without all of its arguments. A new function is returned which takes the remaining arguments.
> map (+ 1) [1,2,3] [2,3,4] > let addntolist n = map (+ n) > addntolist 8 [1,2,3] [9,10,11]
Algebraic Data Types
Union types done right.
Algebraic Data Types
Union types done right.
data Color = Red | Green | Blue data Maybe a = Nothing | Just a data Tree a = Node (Tree a) (Tree a) | Leaf a
Algebraic Data Types
Union types done right.
data Color = Red | Green | Blue data Maybe a = Nothing | Just a data Tree a = Node (Tree a) (Tree a) | Leaf a
-- Pattern matching is used to access the members. findFirstRed :: Tree (Color, t) -> Maybe t findFirstRed (Leaf (Red, n)) = Just n findFirstRed (Leaf _) = Nothing findFirstRed (Node left right) = case findFirstRed left of Nothing -> findFirstRed right Just n -> Just n
Type Classes Families of functions defined for multiple types. Similar to C++ templates, but polymorphic!
Type Classes Families of functions defined for multiple types. Similar to C++ templates, but polymorphic!
class Show a where show :: a -> String class Monoid a where mempty :: a mappend :: a -> a -> a class Foldable t where fold :: Monoid m => t m -> m
Type Classes Families of functions defined for multiple types. Similar to C++ templates, but polymorphic!
class Show a where show :: a -> String class Monoid a where mempty :: a mappend :: a -> a -> a class Foldable t where fold :: Monoid m => t m -> m
data Tree a = Node (Tree a) (Tree a) | Leaf a instance Foldable Tree where fold (Leaf a) = a fold (Node a b) = fold a ‘mappend‘ fold b > fold (Node (Node (Leaf 3) (Leaf 4)) (Leaf 5)) 12 :: Integer
Type Inferencing Types do not need to be declared. They can be inferred from the surrounding context.
Type Inferencing Types do not need to be declared. They can be inferred from the surrounding context.
> :type read read :: forall a. (Read a) => String -> a > :type 1 1 :: forall t. (Num t) => t
Type Inferencing Types do not need to be declared. They can be inferred from the surrounding context.
> :type read read :: forall a. (Read a) => String -> a > :type 1 1 :: forall t. (Num t) => t
> let add1ToString str = 1 + read str add1ToString :: forall t. (Num t, Read t) => String -> t
Type Inferencing Types do not need to be declared. They can be inferred from the surrounding context.
> :type read read :: forall a. (Read a) => String -> a > :type 1 1 :: forall t. (Num t) => t
> let add1ToString str = 1 + read str add1ToString :: forall t. (Num t, Read t) => String -> t
> add1ToString "22" 23 :: Integer > add1ToString "22" :: Int 23 :: Int
What I Like About Haskell
100 times faster than your typical scripting language Strict typing, no pointers Less (finger) typing! More (compile-error) typing! Garbage collection Programmable control flow Tractable side effects – makes concurrency sane! Software transactional memory
Real World Functional Programming
Applications Research
Formal verification Parallelism Type theory
Commercial
Financial sector (Credit Suisse) Security and encryption (Galois) Hardware design (Bluespec) Procedural city generation (gamr7) Multimedia content creation (Anygma)
Open Source
Languages (GHC, Pugs) Window Manager (xmonad) Source Control (darcs) Build System, Packaging (cabal) Web Applications (happs, hoogle)
FP Issues in Game Development
No console ports Not designed for embedded systems Core loop performance High memory consumption Wrong garbage collector tradeoffs
What could FP be used for?
What could FP be used for? Tools! Parsers, domain specific languages, data munging, build systems, code generation.
What could FP be used for? Tools! Parsers, domain specific languages, data munging, build systems, code generation. Ideas! Region-based garbage collection is a reasonable approach to memory management; Insomniac does this. Purity makes parallel programming sane. Pure is the right default for a parallel world. Many beneficial functional programming idioms can be simulated with C++ templates.
What could FP be used for? Tools! Parsers, domain specific languages, data munging, build systems, code generation. Ideas! Region-based garbage collection is a reasonable approach to memory management; Insomniac does this. Purity makes parallel programming sane. Pure is the right default for a parallel world. Many beneficial functional programming idioms can be simulated with C++ templates. Scripting! Lua is Scheme without the parentheses; easy to program functionally.
Further Reading Haskell book http://book.realworldhaskell.org Official site http://www.haskell.org Package database http://hackage.haskell.org Type theory book http://www.cis.upenn.edu/~bcpierce/tapl/ Questions?
Monads Controlled side effects through typing. A design pattern for combining functions.
-- class Monad m where -return :: a -> m a -(>>=) :: m a -> (a -> m b) -> m b data State s a = State (s -> (s, a)) instance Monad (State s) where return a = State (\s -> (s, a)) (>>=) (State lhs) rhs = State (\s -> case lhs s of (s’, a) -> case rhs a of (State fn) -> fn s’) getState getState putState putState runState runState :: State s s = State (\s -> (s,s)) :: s -> State s () s’ = State (\s -> (s’, ())) :: State s a -> s -> (s, a) (State fn) a = fn a
Monads Now we can glue functions that require a common state together and run them with an initial state!
incrementBy1 = getState >>= \s -> putState (s + 1) morefun_nosugar = incrementBy1 >>= \_ -> getState >>= \s -> incrementBy1 >>= \_ -> -- has no effect putState s -- because we put an old state here morefun_withsugar = do incrementBy1 s runfun 11 :: Integer