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

What I wish the older kids had told me about functional programming

What I wish the older kids had told me about functional programming

They tell you that you should learn a functional programming language because it will change how you think about code. What they don't tell you is how you'll be justifying your "crazy" ideas to your team members forever after that. How do you explain to someone that instead of writing a synchronized block you'd like to create 3 new classes and an interface to model an event stream with callbacks? Why on earth would you want to create an object to wrap potential nulls when an if-statement works just fine? If you haven't taken the plunge into functional yet, let me show you some of the ideas you'll bring back from your journey. If you're already a seasoned functional programmer, we can go over how to introduce the functional paradigms into our everyday work without scrapping it and rewriting the whole thing in OCaml.

Nathan Dotz

August 11, 2014
Tweet

More Decks by Nathan Dotz

Other Decks in Programming

Transcript

  1. What I wish the older kids had told me about

    functional programming a presentation for That Conference 2014 by @nathandotz of @detroitlabs
  2. I did not write GHC I do not know ML

    I’ve never finished a McCarthy paper
  3. I’m just a guy (named Nathan Dotz) I work for

    a development firm (Detroit Labs) I’m dissatisfied with how I learned I fell in love with functional
  4. art for art’s sake math for math’s sake elegance precision

    practical useful serviceable understandable x
  5. The Definition of Functional Programming higher-order functions elimination of state

    elimination of effects elimination of state + elimination of effects often via higher order functions and referential transparency X
  6. mutable variables model a machine-level abstraction immutable values do not

    need thread protection less shared state = more modular code
  7. mutable variables model a machine-level abstraction immutable values do not

    need thread protection less shared state = more modular code modular code = shorter code = better code
  8. var total = 0; ! for (var i = 1;

    i <= 10; i++) { ! total += i; ! }
  9. [..., count] = (num + 1 for num in [1..10])

    var count = (1 to 10).last + 1 var count = 1; while (count <= 10) { count += 1; }
  10. var total = (1 to 10).reduce((x, y) => x +

    y) total = [1..10].reduce (x,y) -> x + y var total = 0; for (var i = 1; i <= 10; i++) { total += i; }
  11. var count = 1; while (count <= 10) { count

    += 1; } var total = 0; for (var i = 1; i <= 10; i++) { total += i; }
  12. mapped = [1..10].map (x) -> x * 5 // [5,

    10, 15, 20, 25, 30, 35, 40, 45, 50] ! mapped1 = (num + 1 for num in [1..10]) // [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] ! [..., count] = mapped1 // 11 ! filtered = [1..10].filter (x) -> x % 2 == 0 // [2, 4, 6, 8, 10] ! total = [1..10].reduce (x,y) -> x + y // 55 ! total = sum(range(1, 10)) // 55
  13. –Simon Peyton-Jones (OSCON 2007) “A program that has no side

    effects whatsoever is a kind of black box […] you press ‘Go’ and all you can tell is the box gets hotter.”
  14. Source.fromFile("build.sbt").map { s => s.toInt } fs.writeFile(“config/application.js”, conf, function(err) {

    if(err) { reconfigureConf(conf); } else { conquerWorld(conf); } }); IO.foreach('testfile') {|x| x.reverse }
  15. let compute = function | None -> "No value" |

    Some x -> sprintf "The value is: %d" x Maybe(User.find_by_email(params[:email])).or(User.anonymous) maybe = resultFromDatabase(request) maybe.bind(function(val) { if (val) { return Maybe.Some(val.prop) } else { return Maybe.None() } })
  16. def grow = (s:String) => s + " makes you

    better!" // grow: String => String
  17. Holden("a new perspective").with(grow) // res1: Holden[String] = // Holden(a new

    perspective makes you better!) def grow = (s:String) => s + " makes you better!" Holden().with(grow) // res2: Holden[Nothing] = Holden()
  18. public class User { private String name; private String email;

    private String network; ! public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getNetwork() { return network; } public void setNetwork(String network) { this.network = network; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", email='" + email + '\'' + ", network='" + network + '\'' + '}'; } }
  19. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { ! User user = new User(); user.setName("sleepynate"); user.setEmail("[email protected]"); user.setNetwork("facebook"); System.out.println(user); ! } }
  20. public class ExternalService { public static User getUser(String token) {

    User user = new User(); user.setName("sleepynate"); user.setEmail("[email protected]"); user.setNetwork("facebook"); return user; } } ! public class SuperSpiffyWebApp { public static void main(String[] args) { User user = ExternalService.getUser("50M370KEN"); System.out.println(user); } }
  21. public class ExternalService { public static User getUser(String token) {

    Random random = new Random(); if(random.nextBoolean()) { return null; } else { User user = new User(); if(random.nextBoolean()) { user.setName("sleepynate"); } if(random.nextBoolean()) { user.setEmail("[email protected]"); } if(random.nextBoolean()) { user.setNetwork("facebook"); } return user; } } }
  22. public final class User { public final String name; public

    final String email; public final String network; ! private User() throws Exception { throw new Exception("not allowed"); } ! private User(String name, String email, String network) throws Exception { if(name == null || email == null || network == null) { throw new Exception("null field!"); } this.name = name; this.email = email; this.network = network; } ! public static User build(String name, String email, String network) throws Exception { return new User(name, email, network); } ! }
  23. public static User setName(User user, String name) throws Exception {

    return new User(name, user.email, user.network); } ! public static User setEmail(User user, String email) throws Exception { return new User(user.name, email, user.network); } ! public static User setNetwork(User user, String network) throws Exception { return new User(user.name, user.email, network); }
  24. private User(String name, String email, String network) { name =

    (name == null) ? "defaultName" : name; email = (email == null) ? "defaultEmail" : email; network = (network == null) ? "defaultNetwork" : network; this.name = name; this.email = email; this.network = network; }
  25. public class ThereIsA<T> extends MaybeThereIsA<T> { ! private T theT;

    ! ThereIsA(T value) { this.theT = value; } ! @Override public T get() { return theT; } ! @Override public boolean isThere() { return true; } } ! ! ! public class ThereIsNotA<T> extends MaybeThereIsA<T> { ! @Override public T get() { throw new IllegalStateException(); } ! @Override public boolean isThere() { return false; } }
  26. public abstract class MaybeThereIsA<T> { public abstract T get(); public

    abstract boolean isThere(); ! MaybeThereIsA() { } ! protected MaybeThereIsA(T value) { wrap(value); } ! public static <T> MaybeThereIsA<T> wrap(T value) { if(value == null) return new ThereIsNotA<T>(); else return new ThereIsA<T>(value); } }
  27. public class ExternalService { public static MaybeThereIsA<User> getUser(String token) {

    Random random = new Random(); ! if(random.nextBoolean()) { return MaybeThereIsA.wrap((User) null) ; } else { return MaybeThereIsA.wrap(User.build(“…", “…", “…")); } } } ! ! public class SuperSpiffyWebApp { ! public static void main(String[] args) { ! MaybeThereIsA<User> maybeThereIsAUser = ExternalService.getUser(“…”); ! if(maybeThereIsAUser.isThere()) System.out.println(maybeThereIsAUser.get()); else System.out.println("No user found!"); } }
  28. public abstract class Option<T> { public abstract T get(); public

    abstract boolean isThere(); ! Option() { } ! protected Option(T value) { wrap(value); } ! public static <T> Option<T> wrap(T value) { if(value == null) return new None<T>(); else return new Some<T>(value); } } public class Some<T> extends Option<T> { private T theT; Some(T value) { this.theT = value; } @Override public T get() { return theT; } @Override public boolean isThere() { return true; } } ! ! ! public class None<T> extends Option<T> { @Override public T get() { throw new IllegalStateException(); } @Override public boolean isThere() { return false; } }
  29. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { ! MaybeThereIsA<User> maybeThereIsAUser = ExternalService.getUser(“…”); ! if(maybeThereIsAUser.isThere()) System.out.println(maybeThereIsAUser.get()); else System.out.println("No user found!"); } }
  30. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { ! Function1<Option<User>, Boolean> f = new Function1<Option<User>, Boolean>() { ! @Override public Boolean apply(Option<User> input) { return input.isThere(); } }; ! Option<User> user = ExternalService.getUser(“50M370KEN"); ! if(f.apply(user)) System.out.println(user.get()); else System.out.println("No user found!"); } }
  31. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { ! Function1<Option<User>, Void> f = new Function1<Option<User>, Void>() { ! @Override public Void apply(Option<User> input) { ! if(input.isThere()) System.out.println(input.get()); else System.out.println("No user found!"); return null; ! } }; f.apply(ExternalService.getUser("50M370KEN")); } }
  32. public abstract class Option<T> { public abstract T get(); public

    abstract boolean isThere(); ! Option() { } ! protected Option(T value) { wrap(value); } ! public static <T> Option<T> wrap(T value) { if(value == null) return new None<T>(); else return new Some<T>(value); } ! public <O> Option<O> map(Function1<Option<T>, O> f) { ! if(isThere()) return wrap(f.apply(this)); else return ((Option<O>) this); ! } }
  33. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { ! Function1<Option<User>, Void> f = new Function1<Option<User>, Void>() { ! @Override public Void apply(Option<User> input) { System.out.println(input.get()); return null; } }; ExternalService.getUser("50M370KEN").map(f); } }
  34. public abstract class Option<T> { public abstract T get(); public

    abstract boolean isThere(); ! Option() { } ! protected Option(T value) { wrap(value); } ! public static <T> Option<T> wrap(T value) { if(value == null) return new None<T>(); else return new Some<T>(value); } ! public <O> Option<O> map(Function1<Option<T>, O> f) { ! if(isThere()) return wrap(f.apply(this)); else return ((Option<O>) this); } }
  35. ! ! ! ! ! ! ! ! public interface

    Mappable<T> { public <O> Mappable<O> map(Function1<T, O> f); } ! ! public abstract class Option<T> implements Mappable<T> { public abstract T get(); public abstract boolean isThere(); ! Option() { } ! protected Option(T value) { wrap(value); } ! public static <T> Option<T> wrap(T value) { if(value == null) return new None<T>(); else return new Some<T>(value); } ! }
  36. public class Some<T> extends Option<T> { private T theT; Some(T

    value) { this.theT = value; } @Override public T get() { return theT; } @Override public boolean isThere() { return true; } ! @Override public <O> Option<O> map(Function1<T, O> f) { return wrap(f.apply(this.get())); } ! } ! public class None<T> extends Option<T> { @Override public T get() { throw new IllegalStateException(); } @Override public boolean isThere() { return false; } ! @Override public <O> Mappable<O> map(Function1<T, O> f) { return (Mappable<O>) this; } }
  37. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { ! Function1<User, Void> f = new Function1<User, Void>() { @Override public Void apply(User input) { System.out.println(input); return null; } }; ExternalService.getUser("50M370KEN").map(f); } } !
  38. public interface Function2<I,A,O> { O apply(I input, A operand); }

    ! Function2<User, String, Option<User>> f2 = new Function2<User, String, Option<User>>() { @Override public Option<User> apply(User input, String name) { return Option.wrap(User.setName(input, name)); } };
  39. public interface FlatMappable<T> { public <A,O extends FlatMappable> O flatMap(Function2<T,

    A, O> f, A operand); } ! public abstract class Option<T> implements Mappable<T>,FlatMappable<T> { public abstract T get(); public abstract boolean isThere(); ! Option() { } ! protected Option(T value) { wrap(value); } ! public static <T> Option<T> wrap(T value) { if(value == null) return new None<T>(); else return new Some<T>(value); } ! }
  40. public class None<T> extends Option<T> { @Override public T get()

    { throw new IllegalStateException(); } @Override public boolean isThere() { return false; } @Override public <O> Mappable<O> map(Function1<T, O> f) { return (Mappable<O>) this; } ! @Override public <A, O extends FlatMappable> O flatMap(Function2<T, A, O> f, A operand) { return (O) this; } } ! public class Some<T> extends Option<T> { private T theT; Some(T value) { this.theT = value; } @Override public T get() { return theT; } @Override public boolean isThere() { return true; } @Override public <O> Option<O> map(Function1<T, O> f) { return wrap(f.apply(this.get()));} ! @Override public <A, O extends FlatMappable> O flatMap(Function2<T, A, O> f, A operand) { return (O) f.apply(this.get(), operand); } }
  41. public class SuperSpiffyWebApp { ! public static void main(String[] args)

    { Function1<User, Void> f1 = new Function1<User, Void>() { @Override public Void apply(User input) { System.out.println(input); return null; } }; Function2<User, String, Option<User>> f2 = new Function2<User, String, Option<User>>() { @Override public Option<User> apply(User input, String name) { return Option.wrap(User.setName(input, name)); } }; ExternalService.getUser("50M370KEN").flatMap(f2, "newName").map(f1); } }
  42. public interface Mappable<T> { public <O, F extends Function> Mappable<O>

    map(F f); } ! public interface FlatMappable<T> { public <F extends Function, O extends FlatMappable> O flatMap(F f); }
  43. public interface Functor<T> { public <O, F extends Function> Functor<O>

    map(F f); } ! public interface Monad<T> { public <A, F extends Function, O extends Monad> O flatMap(F f); }
  44. Function<String, Integer> lengthFunction = new Function<String, Integer>() { ! public

    Integer apply(String string) { return string.length(); } }; ! Predicate<String> allCaps = new Predicate<String>() { ! public boolean apply(String string) { return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string); } };
  45. ListMultimap<String, String> firstNameToLastNames; ! ListMultimap<String, String> firstNameToName = Multimaps.transformEntries( firstNameToLastNames,

    new EntryTransformer<String, String, String> () { public String transformEntry(String firstName, String lastName) { return firstName + " " + lastName; } ! });
  46. import static fj.data.Array.array; import static fj.pre.Show.arrayShow; import static fj.pre.Show.intShow; import

    static fj.function.Integers.even; public final class Array_filter { public static void main(final String[] args) { final Array<Integer> a = array(97, 44, 67, 3, 22, 90, 1, 77, 98, 1078, 6, 64, 6, 79, 42); final Array<Integer> b = a.filter(even); arrayShow(intShow).println(b); // {44,22,90,98,1078,6,64,6,42} } import fj.F; import fj.data.Option; import static fj.data.Option.none; import static fj.data.Option.some; import static fj.pre.Show.intShow; import static fj.pre.Show.optionShow; public final class Option_bind { public static void main(final String[] args) { final Option<Integer> o2 = some(8); ! final Option<Integer> p2 = o2.bind(new F<Integer, Option<Integer>>() { public Option<Integer> f(final Integer i) { if(i % 2 == 0) return some(i * 3); else return none(); } }); optionShow(intShow).println(p2); // Some(24) } }
  47. Every expression returns values, use them! Avoid updates, make new

    things
 Don’t use bang methods Send and accept blocks everywhere
  48. RubyVM::InstructionSequence.compile_option = { :tailcall_optimization => true, :trace_instruction => false, }

    ! module Math def self.factorial(n, acc=1) n < 1 ? acc : factorial(n-1, n*acc) end end