Moisés Gabriel Cachay Tello
Creator, destructor.
Haskell
A primer
@xpktro - Functional Programming Perú
Original Version:
Por qué deberías aprender programación funcional ya mismo. - Andrés Marzal
Background: Constraint Based Design
Mario Lavista
La vida (a) leve
Para Ulalume
Y
se
oía
Berg
Dufai
Mozart
Debussy
Schumann
Beethoven
Stravinski
Lutoslavski:
orquestarías
estudiándolos
brillantísimas
extraordinarias
transformaciones
entremezclándoles
pluridimensionales
pseudododecafónicas
sobreornamentaciones
hipermeldessonhnianas.
OuLiPo ('60)
Literature based on very aggressive constraints
Proposal: OuProgPo
Introducing Functional Programming
Sales Pitch
More and more functional programming features are being included in common programming languages.
Mathematic Foundation
Functional programming is based on two mathematical principles:
Lambda Calculus
Category Theory
$$ \lambda x.x^{2} $$
$$ (\lambda x.x^{2}) \; 2 = 4 $$
Lambda Calculus
A formal system for representing and operating functions
$$ \lambda x.x^{2} $$
$$ (\lambda x.x^{2}) \; 2 = 4 $$
$$ \lambda (x, y).x^{2}+y^{2} $$
$$ \lambda x. \lambda y.x^{2}+y^{2} $$
$$ \lambda x.(\lambda y.x^{2}+y^{2}) $$
$$ (\lambda x.(\lambda y.x^{2}+y^{2})) \; 4 = \lambda y.16 + y^{2} $$
$$ (\lambda y.16+y^{2}) \; 3 = 25 $$
$$ (\lambda x.\lambda y.x^{2}+y^{2}) \; 4 \; 5= 25 $$
Pure functional features in Haskell
Haskell is the most usable language that includes pure functional features:
Practical Approach: FizzBuzz
A classic programming exercise: write a program which prints the numbers from 1 to 100. When a multiple of 3 is reached, print "fizz!" instead, and for multiples of 5 print "buzz!"; for multiples of 5 and 3 print "fizzbuzz!".
one!
two!
fizz!
four!
buzz!
fizz!
seven!
eight!
fizz!
buzz!
eleven!
fizz!
thirteen!
fourteen!
fizzbuzz!
sixteen!
seventeen!
fizz!
nineteen!
buzz!
fizz!
twenty two!
Practical Approach: FizzBuzz
fizzbuzz :: Int -> String
fizzbuzz n = undefined
"undefined" is a value that derives from every type
fizzbuzz :: Int -> String
fizzbuzz n = "one!"
main = print (fizzbuzz 1)
"main" is a special function that is called when your program is executed (the entry point)
Practical Approach: FizzBuzz
fizzbuzz n = if n == 1 then "one!" else "two!"
main = print (fizzbuzz 2)
"if" is an expression, it must return a value. We can also define our own if function:
ifThenElse :: Bool -> a -> a -> a
ifThenElse cond thenVal elseVal =
case cond of
True -> thenVal
False -> elseVal
fizzbuzz n = ifThenElse (n==1) ("one"++"!") ("two"++"!")
Practical Approach: FizzBuzz
fizzbuzz :: Int -> String
fizzbuzz n | n == 1 = "one!"
fizzbuzz n | n /= 1 = "two!"
Refactoring time! introducing guards:
Being that the second condition will always succeed for n != 1, we can write it like this:
fizzbuzz n | n == 1 = "one!"
| True = "two!"
-- Or:
fizzbuzz n | n == 1 = "one!"
| otherwise = "two!"
Practical Approach: FizzBuzz
fizzbuzz :: Int -> String
fizzbuzz 1 = "one!"
fizzbuzz n = "two!"
When guards are of the form "n == something", they can be written in a cleaner way:
And if a parameter is not important in a guard, we can replace it with _
fizzbuzz :: Int -> String
fizzbuzz 1 = "one!"
fizzbuzz _ = "two!"
Practical Approach: FizzBuzz
lessThan20 :: Int -> String
lessThan20 n
| n > 0 && n < 20 =
let answers = words ("one two three four five six seven eight nine ten " ++
"eleven twelve thirteen fourteen fifteen sixteen " ++
"seventeen eighteen nineteen")
in answers !! (n-1)
In english, numbers that are less than 20 have proper names, let's make a function for representing those:
words :: String -> [String]
Practical Approach: FizzBuzz
tens :: Int -> String
tens n
| n >= 2 && n <= 9 =
answers !! (n-2)
where
answers = words "twenty thirty forty fifty sixty seventy eighty ninety"
We also need a function for getting names for tens:
"let in" and "where" are interchangeable and used depending on code legibility
Practical Approach: FizzBuzz
number :: Int -> String
number n
| 1 <= n && n < 20 = lessThan20 n
| n `mod` 10 == 0 && n < 100 = tens (n `div` 10)
| n < 100 = tens (n `div` 10) ++ " " ++ lessThan20 (n `mod` 10)
| n == 100 = "one hundred"
Combining our two functions into one:
Guards become an instant useful and elegant construct for grouping a complex set of conditions
Practical Approach: FizzBuzz
main = putStr (unlines (map number [1..30]))
Checking what we have so far by printing it:
unlines :: [String] -> String
map :: (a -> b) -> [a] -> [b]
-- For example:
map (\x -> x * 2) [1,2,3]
-- [2,4,6]
"unlines" joins a string using the newline character
"map" takes a function and applies it to every element of a list
Practical Approach: FizzBuzz
fizzbuzz :: Int -> String
fizzbuzz n
| n `mod` 3 == 0 && n `mod` 5 == 0 = "fizzbuzz!"
| n `mod` 3 == 0 = "fizz!"
| n `mod` 5 == 0 = "buzz!"
| otherwise = number n ++ "!"
main = putStr (unlines (map fizzbuzz [1..100]))
Final refactor, showing fizz! and buzz! and fizzbuzz!
one!
two!
fizz!
four!
buzz!
fizz!
seven!
eight!
fizz!
buzz!
eleven!
fizz!
thirteen!
fourteen!
fizzbuzz!
sixteen!
seventeen!
fizz!
nineteen!
buzz!
fizz!
twenty two!
twenty three!
fizz!
buzz!
twenty six!
fizz!
twenty eight!
twenty nine!
fizzbuzz!
thirty one!
thirty two!
fizz!
thirty four!
buzz!
fizz!
thirty seven!
thirty eight!
fizz!
buzz!
forty one!
fizz!
forty three!
forty four!
fizzbuzz!
forty six!
forty seven!
fizz!
forty nine!
buzz!
fizz!
fifty two!
fifty three!
fizz!
buzz!
fifty six!
fizz!
fifty eight!
fifty nine!
fizzbuzz!
sixty one!
sixty two!
fizz!
sixty four!
buzz!
fizz!
sixty seven!
sixty eight!
fizz!
buzz!
seventy one!
fizz!
seventy three!
seventy four!
fizzbuzz!
seventy six!
seventy seven!
fizz!
seventy nine!
buzz!
fizz!
eighty two!
eighty three!
fizz!
buzz!
eighty six!
fizz!
eighty eight!
eighty nine!
fizzbuzz!
ninety one!
ninety two!
fizz!
ninety four!
buzz!
fizz!
ninety seven!
ninety eight!
fizz!
buzz!
Practical Approach: Bowling Scoring
For this demonstration, we'll be scoring a bowling game:
Frame | Pins | Kind | Bonus | Score | Total |
1 | 1, 4 | Open | 1+4=5 | 0+5=5 | |
2 | 4, 5 | Open | 4+5=9 | 5+9=14 | |
3 | 6, 4 | Spare | 5 | 6+4+5=15 | 14+15=29 |
4 | 5, 5 | Spare | 10 | 5+5+10=20 | 29+20=49 |
5 | 10 | Strike | 0, 1 | 10+0+1=11 | 49+11=60 |
6 | 0, 1 | Open | 0+1=1 | 60+1=61 | |
7 | 7, 3 | Spare | 6 | 7+3+6=16 | 61+16=77 |
8 | 6, 4 | Spare | 10 | 6+4+10=20 | 77+20=97 |
9 | 10 | Strike | 2, 8 | 10+2+8=20 | 97+20=117 |
10 | 2, 8 | Spare | 6 | 2+8+6=16 | 117+16=133 |
Practical Approach: Bowling Scoring
We'll receive a list of knocked-down pins for every throw:
[1, 4, 4, 5, 6, 4, 5, 5, 10, 0, 1, 7, 3, 6, 4, 10, 2, 8, 6]
We'll create a Frame data type to represent a frame and scoring these throws
toFrames :: [Int] -> [Frame]
score :: [Frame] -> Int
Practical Approach: Bowling Scoring
An Algebraic Data Type is defined with all it's possible values, each separated with a | character
data Frame = Open Int Int
| Spare Int Int
| Strike Int Int
toFrames :: [Int] -> [Frame]
toFrames pins = undefined
data Frame = Open { pins1 :: Int
, pins2 :: Int
}
| Spare { pins1 :: Int
, bonus1 :: Int
}
| Strike { bonus1 :: Int
, bonus2 :: Int
}
Practical Approach: Bowling Scoring
A class is a set of behaviors (functions) that any data type can implement (be a instance of)
class Eq a where
(==) :: a -> a -> Bool
x == y = not (x /= y)
(/=) :: a -> a -> Bool
x /= y = not (x == y)
instance Eq Frame where
Open x y == Open x' y' = x == x' && y == y'
Spare x y == Open x' y' = x == x' && y == y'
Strike x y == Open x' y' = x == x' && y == y'
_ == _ = False
Practical Approach: Bowling Scoring
Haskell compiler can actually generate code for some common class derivations
data Frame = Open Int Int
| Spare Int Int
| Strike Int Int
deriving (Eq, Show)
instance Show Frame where
show (Open x y) = "Open " ++ show x ++ " " ++ show y
show (Spare x y) = "Spare " ++ show x ++ " " ++ show y
show (Strike x y) = "Strike " ++ show x ++ " " ++ show y
Practical Approach: Bowling Scoring
We match the argument of our function with the form (first : second : rest)
toFrames :: [Int] -> [Frame]
toFrames (x:y:ys) = Open x y : toFrames ys
toFrames [] = []
":" is the constructor operator for lists:
(:) :: a -> [a] -> [a]
-- [1, 2, 3] == 1:2:3:[]
Practical Approach: Bowling Scoring
Let's consider the concept of Spare
toFrames (x:y:z:ys)
| x+y == 10 = Spare x z : toFrames (z:ys)
| otherwise = Open x y : toFrames (z:ys)
toFrames [x, y] = [Open x y]
toFrames [] = []
Here we're using guards again and add a new definition for 2 element lists.
Practical Approach: Bowling Scoring
When a Spare is made at the final frame, an additional throw must be made, so we need to track the current frame number.
toFrames pins = go 1 pins
where
go 10 [x, y] = [Open x y]
go 10 [x, y, z]
| x + y == 10 = [Spare x z]
go n (x:y:z:ys)
| x + y == 10 = Spare x z : go (n+1) (z:ys)
| otherwise = Open x y : go (n+1) (z:ys)
Practical Approach: Bowling Scoring
Finally, the Strike type is considered:
toFrames :: [Int] -> [Frame]
toFrames pins = go 1 pins
where
go 10 [x, y] = [Open x y]
go 10 [x, y, z]
| x == 10 = [Strike y z]
| x + y == 10 = [Spare x z]
go n (x:y:z:ys)
| x == 10 = Strike y z : go (n+1) (y:z:ys)
| x + y == 10 = Spare x z : go (n+1) (z:ys)
| otherwise = Open x y : go (n+1) (z:ys)
Practical Approach: Bowling Scoring
Something is missing, what is we run out of numbers of if a part of them is inconsistent?
toFrames :: [Int] -> Maybe [Frame]
"Maybe" is a type used to represent 'optional' results:
data Maybe a = Just a
| Nothing
Practical Approach: Bowling Scoring
Making our function return a "Maybe" value:
toFrames :: [Int] -> Maybe [Frame]
toFrames pins = go 1 pins
where
go 10 [x, y]
| x + y < 10 = Just [Open x y]
| otherwise = Nothing
go 10 [x, y, z]
| x == 10 = Just [Strike y z]
| x + y == 10 = Just [Spare x z]
| otherwise = Nothing
go n (x:y:z:ys)
| x == 10 = fmap (Strike y z :) (go (n+1) (y:z:ys))
| x + y == 10 = fmap (Spare x z :) (go (n+1) (z:ys))
| x + y < 10 = fmap (Open x y :) (go (n+1) (z:ys))
| otherwise = Nothing
go _ _ = Nothing
Practical Approach: Bowling Scoring
Finally, our scoring function is made:
frameScore :: Frame -> Int
frameScore (Open x y) = x + y
frameScore (Spare _ y) = 10 + y
frameScore (Strike x y) = 10 + x + y
score :: [Frame] -> Int
score frames = sum (map frameScore frames)
main = print (score frames)
where
Just frames = toFrames throws
throws = [1, 4, 4, 5, 6, 4, 5, 5, 10, 0, 1, 7, 3, 6, 4, 10, 2, 8, 6]
-- > 133
What now?
Many things were left behind for you to research:
?
By Moisés Gabriel Cachay Tello
An introduction to Haskell, based on "Por qué deberías aprender programación funcional ya mismo", a talk from Andrés Marzal.