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

You Wouldn't Fold a Tree...?

Quil
December 03, 2019

You Wouldn't Fold a Tree...?

Functional programming uses a lot of recursion and pattern matching to transform inductive data, but sometimes our functions are mostly plumbing data around which makes it hard to understand what the important part of the transformation is. We’ll look at some techniques that help with these patterns, and how they can be applied to lists and different kinds of trees.

Links:
- Functional Programming with Bananas, Lenses, Envelopes, and Barbed Wire: https://maartenfokkinga.github.io/utwente/mmf91m.pdf

- Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming: https://www.microsoft.com/en-us/research/wp-content/uploads/2003/01/hmap.pdf

- The implementation of these combinators in Erlang: https://github.com/robotlolita/generique

Quil

December 03, 2019
Tweet

More Decks by Quil

Other Decks in Programming

Transcript

  1. The (FP) Agenda WHY? (motivation) FOLDS (you might know this

    already!) GENERALISED FOLDS I. II. III.
  2. FP IS REALLY GOOD AT THIS DEFINING DATA type list(A)

    :: empty | {cons, A, list(A)}. TRANSFORMING DATA rest(empty) -> empty; rest({cons, _, Rest}) -> Rest.
  3. LISTS ARE A TREE type list(A) :: empty | {cons,

    A, list(A)} 1 CONS 2 CONS 3 CONS EMPTY
  4. 1 fib(0) fib(3) fib(2) fib(1) 1 fib(1) 1 RECURSION IS

    A TREE fib(0) -> 0; fib(1) -> 1; fib(N) -> fib(N-1) + fib(N-2).
  5. TRANSFORMING ELEMENTS OF A LIST map({cons, X, Rest}, Fun) ->

    {cons, Fun(X), map(Rest, Fun)}; map(empty, Fun) -> empty.
  6. REMOVING ELEMENTS FROM A LIST filter({cons, X, Rest}, Pred) ->

    case Pred(X) of true -> {cons, X, filter(Rest, Pred)}; false -> filter(Rest, Pred) end; filter(empty, Pred) -> empty.
  7. CONCATENATING TWO LISTS concat({cons, X, Xs}, Ys) -> {cons, X,

    concat(Xs, Ys)}; concat(empty, Ys) -> Ys.
  8. CAPTURING THE COMMON PATTERN fold(Cons, Empty, {cons, X, Rest}) ->

    Cons(X, fold(Cons, Empty, Rest)); fold(Cons, Empty, empty) -> Empty().
  9. TRANSFORMING ELEMENTS OF A LIST map(List, Fun) -> fold( fun(X,

    Xs) -> {cons, Fun(X), Xs} end, fun() -> empty end, List ).
  10. REMOVING ELEMENTS FROM A LIST filter(List, Pred) -> fold( fun(X,

    Xs) -> case Pred(X) of true -> {cons, X, Xs}; false -> Xs end end, fun() -> empty end, List ).
  11. SUMMING UP ELEMENTS OF A LIST sum(List) -> fold( fun(A,

    B) -> A + B end, fun() -> 0 end, List ).
  12. CONCATENATING TWO LISTS concat(Xs, Ys) -> fold( fun(A, As) ->

    {cons, A, As} end, fun() -> Ys end, Xs ).
  13. INTERLUDE: LIGHTWEIGHT NOTATIONS map f xs = foldr (\x xs

    -> f x : xs) [] filter p xs = foldr (\x xs -> if p x then x : xs else xs) [] sum xs = foldr (+) 0 concat xs ys = foldr (:) ys xs
  14. OPTIMISING FP Dr. Gabriele Keller and others have been using

    this to optimise functional programs with e.g.: stream fusion.
  15. RIGHT NOW We need to write one fold (and its

    combinators) for each data structure! this doesn’t scale!
  16. I. Visiting every value in the universe ufold([]) -> [];

    ufold([X|Xs]) -> [ufold(X)|ufold(Xs)]; ufold({}) -> {}; ufold({A}) -> {ufold(A)}; ufold({A, B}) -> {ufold(A), ufold(B)};
  17. II. Restricting the universe ufold(T) when is_tuple(T) -> map_tuple(fun ufold/1,

    T); ufold([X|Xs]) -> [ufold(X)|ufold(Xs)]; ufold(M) when is_map(M) -> map_values( fun ufold/1, M); ufold(X) -> X.
  18. III. Transforming the values ufold(F,T) when is_tuple(T) -> map_tuple( fun(X)

    -> ufold(F, X) end, T); ufold(F, [X|Xs]) -> [ufold(F, X)|ufold(F, Xs)]; (...) ufold(F, X) -> F(X).
  19. IV. Transforming ALL of the values ufold(F,T) when is_tuple(T) ->

    F(map_tuple( fun(X) -> ufold(F,X) end, T)); ufold(F, [X|Xs]) -> F([ ufold(F, X) | ufold(F, Xs)]); (...) ufold(F, X) -> F(X).
  20. EXAMPLE 1: SALARY INCREASE Company = {company, [ {dep, “Research”,

    {e, {n, “Ralf”, “Amsterdam”}, {s, 800}}, [ {e, {n, “Joost”, “Amsterdam”}, {s, 100}} , {e, {n, “Marlow”, “Cambridge”}, {s, 200}} ] }, {dep, “Strategy”, {e, {n, “Blair”, “London”}, {s, 100000}}, []} ]}.
  21. EXAMPLE 1: SALARY INCREASE inc({company, Xs}) -> lists:map(fun inc_dep/1, Xs).

    inc_dep({dep, N, M, Ps}) -> {dep, N, inc_emp(M), lists:map(fun inc_emp/1, Ps)}. inc_emp({e, N, {s, X}}) -> {e, N, {s, X * Rate}}.
  22. EXAMPLE 1: SALARY INCREASE Rate = 2, ufold( fun({s, X})

    -> {s, X * Rate}; (X) -> X end, company() ).
  23. EXAMPLE 1: SALARY INCREASE Company = {company, [ {dep, “Research”,

    {e, {n, “Ralf”, “Amsterdam”}, {s, 1600}}, [ {e, {n, “Joost”, “Amsterdam”}, {s, 200}} , {e, {n, “Marlow”, “Cambridge”}, {s, 400}} ] }, {dep, “Strategy”, {e, {n, “Blair”, “London”}, {s, 200000}}, []} ]}.
  24. EXAMPLE 2: ALGEBRAIC SIMPLIFICATION Program = {prog, [ ... {‘fun’,

    f, [x], [ {‘let’, y, {‘+’, x, {‘*’, {int, 3}, {int, 1000}}}, {‘*’, x, {int, 2}}}]}, ... ]}.
  25. EXAMPLE 2: ALGEBRAIC SIMPLIFICATION ufold( fun({‘*’, {int, A}, {int, B}})

    -> {int, A * B}; ({‘*’, X, {int, 2}}) when is_atom(X) -> {‘+’, X, X}; (X) -> X end, Program ).
  26. EXAMPLE 2: ALGEBRAIC SIMPLIFICATION Program = {prog, [ ... {fun,

    f, [x], [ {let, y, {‘+’, x, {int, 3000}}} {‘+’, x, x}}]}, ... ]}.
  27. SMALL VARIANT: QUERIES map_query(F, X) when is_tuple(X) -> map_tuple_to_list(F, X);

    map_query(F, [X | Xs]) -> [F(X), F(Xs)]; map_query(F, X) -> []. collect(Combine, Q, X) -> lists:foldl(Combine, Q(X), map_query(fun(X1) -> collect(Combine, Q, X1) end, X)).
  28. EXAMPLE 3: ALL INTEGER NODES Program = {prog, [ ...

    {‘fun’, f, [x], [ {‘let’, y, {‘+’, x, {‘*’, {int, 3}, {int, 1000}}}, {‘*’, x, x}}]}, ... ]}.
  29. EXAMPLE 3: ALL INTEGER NODES >> collect( fun(A, B) ->

    A ++ B end, fun({int, X}) -> [{int, X}]; (_) -> [] end, Program ). [{int, 3}, {int, 1000}]
  30. Most data can be thought of as a tree. 1

    CONS 2 CONS 3 CONS EMPTY
  31. Fold abstracts the recursion in a tree-like structure. fold( fun(A,

    B) -> A + B end, fun() -> 0 end, {cons, 1, {cons, 2, empty}} ).
  32. A universal fold is simply a fold over all possible

    types. ufold(F,T) when is_tuple(T) -> F(map_tuple( fun(X) -> ufold(F,X) end, T)); ufold(F, [X|Xs]) -> F([ ufold(F, X) | ufold(F, Xs)]); (...) ufold(F, X) -> F(X).
  33. This forms a basis for other recursive operators, like querying.

    collect(K, Q, X) -> lists:foldl( K, Q(X), map_query( fun(X1) -> collect(K, Q, X1) end, X)).
  34. It’s context-free; practical usage needs tagged data. ufold( fun (X)

    when is_integer(X) -> X + 1; (X) -> X end, [1, sets:from_list([1, 2])] ). => [2, {set, …}]
  35. AND THEN THERE WERE LINKS... Functional Programming with Bananas, Lenses,

    ... Scrap Your Boilerplate: A Practical Design Pattern ... Generique: Generic Programming for Erlang