Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lambda To The Rescue

turanct
February 26, 2019

Lambda To The Rescue

We took the Red Pill and wanted to find out how deep the rabbit-hole goes​. We started to learn Functional Programming languages like Scheme, Haskell and Erlang and noticed that some of the same problems that we have in Object Oriented Programming are solved in radically different ways in these languages. Since we always want to optimize for developer productivity and away from cognitive overhead, we took some of those solutions that allowed us to think less about implementation and more about problem solving, and tried to port them back to what we already know. The Object. So are you ready to follow the white rabbit that is functional programming? Do you want to know where taking the Red Pill leads you? Or are you just bored and do you want to learn a new language? All reasons are good to join us in this lambda-tastic exploration.

turanct

February 26, 2019
Tweet

More Decks by turanct

Other Decks in Programming

Transcript

  1. functional programming a declarative programming paradigm treats computation as the

    evaluation of mathematical functions avoids changing state and mutable data
  2. avoids changing state and mutable data since it's declarative and

    since it uses functions, state changes are dif cult
  3. "function" cylinders :: Int cylinders = 2 variables don't exist

    in many functional languages this means cylinders can never change value this function is "pure", it has no side-effects
  4. what does this look like in PHP function cylinders() {

    echo rand(); return 2; } this function is not "pure" we can only know if we inspect the function body
  5. If we would want to "echo" in the Haskell example,

    the type of the function would change from cylinders :: Int to cylinders :: IO Int we wouldn't have to inspect the function body to know
  6. transparancy double :: (Num a) => a -> a double

    x = 2 * x double :: (Num a) => a -> a double 0 = 0 double 1 = 2 double 2 = 4 double 3 = 6 double 4 = 8 -- ... etcetera we can look "straight through" the function we can reduce a function to a map of values to other values
  7. back to OOP pure expressions make reasoning easy referential transparancy

    makes reasoning easy how to make side-effects visible? reduced mental overhead
  8. in php: loops to iterate over lists function countWordsInList(array $listOfWords)

    { $count = 0; foreach ($listOfWords as $word) { $count++; } return $count; }
  9. in haskell: recursive functions countWordsInList :: [String] -> Int countWordsInList

    [] = 0 countWordsInList (x:xs) = 1 + countWordsInList xs
  10. in haskell: recursive functions countWordsInList :: [String] -> Int countWordsInList

    [] = 0 countWordsInList (x:xs) = 1 + countWordsInList xs countWordsInList ["foo", "bar", "baz"] = 1 + countWordsInList ["bar", "baz"] countWordsInList ["foo", "bar", "baz"] = 1 + 1 + countWordsInList ["baz"] countWordsInList ["foo", "bar", "baz"] = 1 + 1 + 1 + countWordsInList [] countWordsInList ["foo", "bar", "baz"] = 1 + 1 + 1 + 0 countWordsInList ["foo", "bar", "baz"] = 3
  11. sum of a list of integers sum' :: [Int] ->

    Int sum' [] = 0 sum' (x:xs) = x + sum' xs
  12. doesn't that look familiar? countWordsInList :: [String] -> Int countWordsInList

    [] = 0 countWordsInList (x:xs) = 1 + countWordsInList xs sum' :: [Int] -> Int sum' [] = 0 sum' (x:xs) = x + sum' xs
  13. doesn't that look familiar? xxxxx [] = __value__ -- either

    return something for an empty list xxxxx (x:xs) = __function__ x (xxxxx xs) -- or take the first item of the list and combine -- it with the recursive call with the rest of the list
  14. doesn't that look familiar? xxxxx [] = __value__ -- either

    return something for an empty list xxxxx (x:xs) = __function__ x (xxxxx xs) -- or take the first item of the list and combine -- it with the recursive call with the rest of the list let's de ne a type: fold' :: (b -> a -> a) -> a -> [b] -> a
  15. doesn't that look familiar? xxxxx [] = __value__ -- either

    return something for an empty list xxxxx (x:xs) = __function__ x (xxxxx xs) -- or take the first item of the list and combine -- it with the recursive call with the rest of the list let's de ne a type: fold' :: (b -> a -> a) -> a -> [b] -> a let's write the base case: fold' _ def [] = def
  16. doesn't that look familiar? xxxxx [] = __value__ -- either

    return something for an empty list xxxxx (x:xs) = __function__ x (xxxxx xs) -- or take the first item of the list and combine -- it with the recursive call with the rest of the list let's de ne a type: fold' :: (b -> a -> a) -> a -> [b] -> a let's write the base case: fold' _ def [] = def let's write the other case: fold' func def (x:xs) = func x (fold' func def xs)
  17. fold fold' :: (b -> a -> a) -> a

    -> [b] -> a fold' _ def [] = def fold' func def (x:xs) = func x (fold' func def xs)
  18. remember countWordsInList ? countWordsInList :: [String] -> Int countWordsInList []

    = 0 countWordsInList (x:xs) = 1 + countWordsInList xs
  19. remember countWordsInList ? countWordsInList :: [String] -> Int countWordsInList []

    = 0 countWordsInList (x:xs) = 1 + countWordsInList xs what if we write it using fold ? what do we do each recursion? what is the default value?
  20. how about sum' :: [Int] -> Int ? - what

    do we do each recursion? - what is the default value?
  21. how about sum' :: [Int] -> Int ? sum' ::

    [Int] -> Int sum' xs = fold' (+) 0 xs sum' :: [Int] -> Int sum' = fold' (+) 0
  22. can we write a function maximum' ? it takes a

    list of Int s and returns the biggest Int of the list.
  23. can we write a function maximum' ? - what do

    we do each recursion? - what is the default value?
  24. can we write a function maximum' ? maximum' :: [Int]

    -> Int maximum' = fold' (\next cur -> if next > cur then next else cur) 0
  25. how many years do you have left? yearsLeft takes a

    list of ages and returns a list of years left for every one of those.
  26. how many years do you have left? yearsLeft :: [Int]

    -> [Int] yearsLeft [] = [] yearsLeft (x:xs) = 100 - x : yearsLeft xs yearsLeft [] -- [] yearsLeft [4, 8, 15, 16, 23, 42] -- [96, 92, 85, 84, 77, 58]
  27. get ages of a list of User s data User

    = User { name :: String , age :: Int } ages :: [User] -> [Int] ages [] = [] ages (u:us) = age u : ages us
  28. doesn't that look familiar? yearsLeft :: [Int] -> [Int] yearsLeft

    [] = [] yearsLeft (x:xs) = 100 - x : yearsLeft xs ages :: [User] -> [Int] ages [] = [] ages (u:us) = age u : ages us
  29. doesn't that look familiar? xxxxx [] = [] -- an

    empty input list results in an empty output list xxxxx (x:xs) = __function__ x : (xxxxx xs) -- take the first item of the list and do something with it -- recurse on the rest of the list and append results.
  30. doesn't that look familiar? xxxxx [] = [] -- an

    empty input list results in an empty output list xxxxx (x:xs) = __function__ x : (xxxxx xs) -- take the first item of the list and do something with it -- recurse on the rest of the list and append results. let's de ne a type: map' :: (a -> b) -> [a] -> [b] let's write the base case: map' _ [] = [] let's write the other case: map' func (x:xs) = func x : map' func xs
  31. map map' :: (a -> b) -> [a] -> [b]

    map' _ [] = [] map' func (x:xs) = func x : map' func xs
  32. can we write a function plusOne ? it takes a

    list of Int s and returns a list with all elements of the rst list incremented by 1.
  33. can we write a function plusOne ? it takes a

    list of Int s and returns a list with all elements of the rst list incremented by 1. which function do we want to apply to every element of a list?
  34. can we write a function plusOne ? plusOne :: [Int]

    -> [Int] plusOne = map' (+ 1) plusOne [] -- [] plusOne [1, 2, 3] -- [2,3,4] plusOne [3] -- [4]
  35. back to OOP generalizations over recursive functions! totally declarative way

    of thinking about lists no side-effects or changing state reduced mental overhead
  36. back to OOP generalizations over recursive functions! totally declarative way

    of thinking about lists no side-effects or changing state reduced mental overhead check out array_map and array_reduce
  37. syntactic sugar map reverse (filter (\x -> length x <

    5) ["foo", "bar", "baz", "qux", "ramsam"]) takes the list ["foo", "bar", "baz", "qux", "ramsam"] returns the list ["oof", "rab", "zab", "xuq"] we have to look where the parentheses () are we have to read backwards
  38. syntactic sugar map reverse $ filter (\x -> length x

    < 5) $ ["foo", "bar", "baz", "qux", "ramsam"] introducing $ we still have to read backwards we don't have to think about parentheses
  39. intermezzo: F#'s |> (|>) = flip ($) -- a $

    b = b (flip ($)) a -- ($) a b = (flip ($)) b a
  40. syntactic sugar map reverse $ filter (\x -> length x

    < 5) $ ["foo", "bar", "baz", "qux", "ramsam"] ["foo", "bar", "baz", "qux", "ramsam"] |> filter (\x -> length x < 5) |> map reverse using our newly created |> instead of $ we can read left to right we have clear ow we can "see" the data pipeline
  41. domain language changeAddress :: Client -> Address -> Client let

    abbeyRoad3 = "3 Abbey Road, London NW8 9AY, UK" let updatedClient = changeAddress client abbeyRoad3 doesn't read very well could we use an in x operator here?
  42. domain language changeAddress :: Client -> Address -> Client let

    abbeyRoad3 = "3 Abbey Road, London NW8 9AY, UK" let updatedClient = client `changedAddressTo` abbeyRoad3 changedAddressTo :: Client -> Address -> Client
  43. the absolute minimum (define sum (lambda (list) (cond ((null? list)

    0) (else (+ (car list) (sum (cdr list)))))))
  44. the absolute minimum (define sum (lambda (list) (cond ((null? list)

    0) (else (+ (car list) (sum (cdr list)))))))
  45. back to OOP readable code code that tells a story

    thinking about better names use the language of the domain
  46. How do you hide your internals? <?php namespace Dns; interface

    Client { public function resolve(Request $request): Response; }
  47. How do you hide your internals? <?php namespace Dns; interface

    Client { public function resolve(Request $request): Response; } a Dns Client resolve s a Request and returns a Response how it does that exactly doesn't concern us
  48. hiding data in scheme (define address (list "Toon Daelman" "FooBarStreet

    42" "9000 Ghent" "Belgium")) to get the country: (list-ref address 3)
  49. can we do better? (module address (address country) (import scheme)

    (define address (lambda (name line1 line2 country) (list name line1 line2 country))) (define country (lambda (address) (list-ref address 3))) )
  50. can we do better? (module address (address country) (import scheme)

    (define address (lambda (name line1 line2 country) (vector name line1 line2 country))) (define country (lambda (address) (vector-ref address 3))) ) using vectors, or records, or... consumers don't need to change anything
  51. types in haskell type AddressLine = String type Country =

    String data Address = Address { name :: String , line1 :: AddressLine , line2 :: AddressLine , country :: Country } deriving (Show) countryFrom :: Address -> Country countryFrom = country
  52. types in haskell type AddressLine = String type Country =

    String data Address = Address { name :: String , line1 :: AddressLine , line2 :: AddressLine , country :: Country } deriving (Show) countryFrom :: Address -> Country countryFrom = country check out the countryFrom type signature!
  53. function to check equality (==) :: a -> a ->

    Bool problem: implementation differs per type solution: typeclass
  54. function to check equality class Eq a where (==) ::

    a -> a -> Bool x == y = not (x /= y) (/=) :: a -> a -> Bool x /= y = not (x == y)
  55. function to check equality class Eq a where (==) ::

    a -> a -> Bool x == y = not (x /= y) (/=) :: a -> a -> Bool x /= y = not (x == y) instance Eq Address where x == y = sameAddressLines && sameCountry where sameAddressLines = (line1 x == line1 y) && (line2 x == line2 y) sameCountry = country x == country y
  56. function to check equality data Address = Address { name

    :: String , line1 :: AddressLine , line2 :: AddressLine , country :: Country } deriving (Show) instance Eq Address where x == y = sameAddressLines && sameCountry where sameAddressLines = (line1 x == line1 y) && (line2 x == line2 y) sameCountry = country x == country y if we would care about all the elds, we could be deriving (Eq)
  57. back to OOP interfaces, interfaces, interfaces public methods of a

    class already are its public interface ValueObjects interfaces don't give insight about side-effects $foo == $bar; versus $foo->equals($bar);
  58. ordering some coffees - Lungo € 2.50 - Lungo €

    2.50 - Cappuccino € 2.90 - Double Espresso € 2.10 - Iced Coffee € 4.50 what's the total price?
  59. ordering some coffees - Lungo € 2.50 - Lungo €

    2.50 - Cappuccino € 2.90 - Double Espresso € 2.10 - Iced Coffee € 4.50 2.50 + 2.50 + 2.90 + 2.10 + 4.50 = (2.50 + 2.50) + (2.90 + 2.10) + 4.50 = 5 + 5 + 4.50 = 10 + 4.50 = 14.50
  60. ordering some coffees - Lungo € 2.50 - Lungo €

    2.50 - Cappuccino € 2.90 - Double Espresso € 2.10 - Iced Coffee € 4.50 2.50 + 2.50 + 2.90 + 2.10 + 4.50 = 2.50 + (2.50 + 2.90) + (2.10 + 4.50) = 2.50 + 5.40 + 6.60 = 7.90 + 6.60 = 14.50
  61. ordering some coffees - Lungo € 2.50 - Lungo €

    2.50 - Cappuccino € 2.90 - Double Espresso € 2.10 - Iced Coffee € 4.50 2.50 + 2.50 + 2.90 + 2.10 + 4.50 = ((((2.50 + 2.50) + 2.90) + 2.10) + 4.50) = (((5 + 2.90) + 2.10) + 4.50) = ((7.90 + 2.10) + 4.50) = (10 + 4.50) = 14.50
  62. you don't pay for service - Lungo € 2.50 -

    Lungo € 2.50 - Cappuccino € 2.90 - Double Espresso € 2.10 - Iced Coffee € 4.50 - service € 0
  63. 0 is the neutral element of + the sum (+)

    of a value x and 0 is x we call it identity
  64. do lists and ++ form a monoid? $ ([1, 2]

    ++ [3, 4]) ++ [5, 6] [1, 2, 3, 4, 5, 6] $ [1, 2] ++ ([3, 4] ++ [5, 6]) [1, 2, 3, 4, 5, 6] associativity
  65. do lists and ++ form a monoid? $ ([1, 2]

    ++ [3, 4]) ++ [5, 6] [1, 2, 3, 4, 5, 6] $ [1, 2] ++ ([3, 4] ++ [5, 6]) [1, 2, 3, 4, 5, 6] associativity $ [1, 2] ++ [] [1, 2] neutral element
  66. do lists and ++ form a monoid? $ ([1, 2]

    ++ [3, 4]) ++ [5, 6] [1, 2, 3, 4, 5, 6] $ [1, 2] ++ ([3, 4] ++ [5, 6]) [1, 2, 3, 4, 5, 6] associativity $ [1, 2] ++ [] [1, 2] neutral element
  67. formalizing class Monoid m where mempty :: m -- neutral

    element mappend :: m -> m -> m -- binary function mconcat :: [m] -> m -- foldr mconcat = foldr mappend mempty
  68. a wild php method appeared <?php function words($string) { if

    (empty($string)) { return false; } return explode(' ', $string); } var_dump(words('')); // returns false var_dump(words('foo bar baz')); // returns ['foo', 'bar', 'baz'];
  69. a wild php method appeared <?php function words($string) { if

    (empty($string)) { return false; } return explode(' ', $string); } var_dump(words('')); // returns false var_dump(words('foo bar baz')); // returns ['foo', 'bar', 'baz']; $string = ''; var_dump(explode(' ', $string)); // returns ['']
  70. If I pass words a string, it returns an array

    of words in the string. Except that for empty strings, it will return false .
  71. type annotations function words($string) { // ... } function words(string

    $string): array { // ... } what about the return false ?
  72. what about the return false ? function words(string $string): array

    { if (empty($string)) { return false; // TROUBLEMAKER } return explode(' ', $string); }
  73. what about the return false ? function words(string $string): array

    { if (empty($string)) { return array(); } return explode(' ', $string); }
  74. If I pass words a string it returns an array

    of words in the string. The base case where the string is empty is now logical: an empty string has no words, so a list of 0 words is returned.
  75. chaining calls let's say we have this function as well:

    function chars(string $string): array { if (empty($string)) { return array(); } return str_split($string); } would this then work? chars(words('foo bar baz'));
  76. array_map(@chars, words('foo bar baz')); what's the result of this? [["f","o","o"],["b","a","r"],["b","a","z"]]

    oh no! we wanted this: ["f","o","o","b","a","r","b","a","z"] we need to " atten" the array
  77. atten the array function array_concat(array $arrays): array { return call_user_func_array(

    @array_merge, array_merge(array(array()), $arrays) ); } array_concat(array_map(@chars, words('foo bar baz'))); ["f","o","o","b","a","r","b","a","z"] combining array_map and array_concat
  78. bind function array_bind(array $array, $f) { return array_concat(array_map($f, $array)); }

    array_bind takes a list of values and a function that operates on one of those values and returns a new list of values. “ “
  79. bind function array_bind(array $array, $f) { return array_concat(array_map($f, $array)); }

    array_bind(array_bind(array('foo bar baz'), @words), @chars); // returns ["f","o","o","b","a","r","b","a","z"] array_bind takes a list of values and a function that operates on one of those values and returns a new list of values. “ “
  80. bind function array_bind(array $array, $f) { return array_concat(array_map($f, $array)); }

    array_bind(array_bind(array('foo bar baz'), @words), @chars); // returns ["f","o","o","b","a","r","b","a","z"] array_bind(array_bind(array(''), @words), @chars); // returns [] array_bind takes a list of values and a function that operates on one of those values and returns a new list of values. “ “
  81. bind array_bind(array_bind(array('foo bar baz'), @words), @chars); // returns ["f","o","o","b","a","r","b","a","z"] array_bind(array_bind(array(''),

    @words), @chars); // returns [] pure "foo bar baz" >>= words >>= chars pure "" >>= words >>= chars
  82. bind pure "foo bar baz" >>= words >>= chars pure

    "" >>= words >>= chars pure is the context >>= is the bind in x operator words :: String -> [String] chars :: String -> [String] it reads so much better than the PHP version
  83. what if function div(int $divident, int $divisor): WrappedValue { if

    ($divisor === 0) { return nothing(); } return pure(intdiv($divident, $divisor)); } function square(int $number): WrappedValue { return pure($number * $number); } var_dump(div(10, 2)->bind(@square)); // [25] var_dump(div(10, 0)->bind(@square)); // []
  84. back to OOP exeption handling, null/false checks, third option Null

    Objects think about how your functions behave!