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

Learning Functional Programming Without Growing...

Kelsey Innis
December 13, 2012

Learning Functional Programming Without Growing a Neckbeard

Learn how to write code that's like Pam Grier--powerful, beautiful, and tough to mess with.

Kelsey Innis

December 13, 2012
Tweet

Other Decks in Programming

Transcript

  1. HI! I'm Kelsey. I work at StackMob writing code that

    lets our users create & manage their applications, APIs, and data through the browser. I use Scala and the Lift web framework to do that.
  2. THE PAM GRIER CRITERIA FOR CODE BADASSERY Code, like Pam

    Grier, should be: Powerful, Beautiful, and Tough to mess with.
  3. TOUGH TO MESS WITH Secure from outside manipulation Hard to

    unintentionally make mistakes Easy to maintain
  4. SCALA! don't need parentheses for method calls semicolons are optional

    return value is the last line of the method static typing with type inference
  5. BUT WHAT DOES IT LOOK LIKE? val x: String =

    "a" val y = 2 val z = 15.3 val myThing = new Thing val theList = List("this", "sure", "is", "a", "list") val whatIsIt = theList(4)
  6. BUT WHAT DOES IT LOOK LIKE? def haggle(theirPrice: Int, myOffer:

    Int): String = { val theResponse: String = if (theirPrice <= myOffer + 5) { "You've got a deal!" } else { "I definitely wouldn't pay more than " + (myOffer + (theirPrice - myOffer)/2) + " for it." } theResponse } val askingPrice = 100 val iWantToPay = 50 val letsMakeADeal = haggle(askingPrice, iWantToPay)
  7. A computer program is a list of instructions that tell

    a computer what to do. ASK A KID (OR SIMPLE ENGLISH WIKIPEDIA) WRONG
  8. Imperative programming: a sequence of commands that the computer carries

    out in sequence Object-oriented programming: these instructions, and the data they manipulate, are organized into objects
  9. —comp.lang.functional FAQ “Functional programming is a style of programming that

    emphasizes the evaluation of expressions rather than the execution of commands.”
  10. WHAT THINGS ARE but also... val theQueen = "Elizabeth II"

    def theGovernor(state: State) = { val candidates = state.getCandidates candidates(getTopVoteGetter) }
  11. WHAT IS A FUNCTION? A function is a relation between

    values where each of its input values gives back exactly one output value, playa.
  12. A function is pure if “the impact of a function

    on the rest of the program [can] be described only in terms of its return type, and...the impact of the rest of the program on the function be described only in terms of its arguments”. (Victor Nicollet)
  13. FUNCTIONS ARE DETERMINISTIC. You will always get the same result

    if you run them with the same data. correctness is more clear unit tests are a breeze debugging is more directed tough to mess with
  14. FUNCTIONS ARE ENCAPSULATED. “With a referentially transparent function, the interface-

    level activity is all one needs to know about its behavior.”. (Michael O. Church) readability reuse maintainability beautiful tough to mess with
  15. FUNCTIONS ARE COMMUTATIVE. parallelization concurrency lazy evaluation val firstThing =

    doOneThing() val secondThing = doAnotherThing() val thirdThing = doTheLastThing(firstThing, secondThing) powerful
  16. DATA IS IMMUTABLE. Once an object is created, it cannot

    be changed. If you need to change an object, make your own copy. a quick detour back to Java... concurrency rollback of data simplicity String s1 = "san dimas high school football rules" String s2 = s1.toUpperCase println("string 1: " + s1); println("string 2: " + s2); powerful tough to mess with
  17. FIRST CLASS CITIZENS val longSkinnyThing: String = "this is a

    string" val listOfThem: List[String] = List("yarn","twine","thread") val freshNewLongSkinnyThing: String = spinFromFiber("wool") tieInAKnot(longSkinnyThing) class Rope(type:String) { override def toString(): String = "You've put me on a diet!"; } val longSkinnyThing: Rope = new Rope("nautical") val listOfThem: List[String] = List(longSkinnyThing, new Rope("climbing"), new Rope("clothesline"), new Rope("jump")) val freshNewLongSkinnyThing: Rope = spinFromFiber("hemp") tieInAKnot(longSkinnyThing)
  18. addSpam's type is (String) => String (list of parameters' types)

    => return type val addSpam: (String) => String = { (x:String) => x + " and Spam" } addSpam("Egg and Bacon") //result: "Egg and Bacon and Spam" val menuOptions = List(addSpam, withoutSpam) menuOptions(1)("Egg and Bacon and Spam") //result: "You can't have that"
  19. RETURNING FUNCTIONS FROM FUNCTIONS def tagText(tag: String, text: String) =

    "<" + tag +">" + text + "" val noReally = tagText("em", "pay attention!!!!") //result: <em>pay attention!!!!</em> def tagText2(tag: String) = { (text:String) =>"<" + tag +">" + te xt + "" } val tagWithAndSpam = tagText2("andSpam") val breakfast = tagWithAndSpam("Spam Bacon and Sausage") //result: <andSpam>Spam Bacon and Sausage</andSpam> beautiful powerful
  20. FOR LOOP Java Scala public void talkAboutFruit { Fruit[] fruits

    = { new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry") }; for (int i = 0; i < fruits.length; i++) { System.out.println("Hey the other day I ate a " + fruits[i]; } } def talkAboutFruit = { val fruits = List(new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry")) for (i <- 0 until fruits.length) { System.out.println("Hey the other day I ate a " + fruits(i); } }
  21. LET'S GET ABSTRACT a function that takes a list and

    a function (list of parameters' types) => return type foreach(fruitList:List(fruits), theFunction: (Fruit) => Unit): Unit def foreach(fruitList:List(fruits), theFunction: (Fruit) => Unit ) = { for (i <- 0 until fruitList.length) { theFunction(fruits(i)) } } def talkAboutFruit = { val fruits = List(new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry")) val tellEm = { (f:Fruit) => System.out.println( "Hey the other day I ate a " + f) } foreach(fruits, tellEm) } }
  22. MORE ABSTRACTERER! foreach(theList:List(A), theFunction: (A) => Unit): Unit abstract class

    Collection[A] { ... def foreach(theFunction: (A) => Unit): Unit ... } def talkAboutFruit = { val fruits = List(new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry")) val tellEm = { (f:Fruit) => System.out.println( "Hey the other day I ate a " + f) } fruits.foreach(tellEm) } }
  23. THIS: IS NOT HOW SCALA IMPLEMENTED FOREACH abstract class Collection[A]

    { ... def foreach(theFunction: (A) => Unit): Unit = { for (i <- 0 until this.length) { theFunction(this(i)) } } ... } tough to mess with powerful
  24. SOMETHING A LITTLE JUICIER on a collection of A, you

    can map(theFunction: (A) => B): Collection[B] def makePies: List[Pie] = { val fruits = List(new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry")) var pies = List() for (i <- 0 until fruits.length) { new Pie(fruits(i)) :: pies } pies } def makePies: List[Pie] = { val fruits = List(new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry")) val makePie = { (f: Fruit) => new Pie(f) } fruits.map(makePie) }
  25. ANONYMOUS FUNCTIONS val kindOfFruit: String = "blueberry" val blueberryFruit =

    new Fruit(kindOfFruit) val alsoBlueberry = new Fruit("blueberry") val makePie = { (f: Fruit) => new Pie(f) } fruits.map(makePie) //equivalent to fruits.map( { (f: Fruit) => new Pie(f) } ) def makePies: List[Pie] = { val fruits = List(new Fruit("apple"), new Fruit("cherry"), new Fruit("strawberry")) fruits.map( { (f: Fruit) => new Pie(f) } ) } def makePies(fruits: List[Fruit]) : List[Pie] = fruits.map( { (f: Fruit) => new Pie(f) } ) beautiful
  26. COLLECTION HANDLING FILTER FOLD REDUCE val theList = List(new Fruit("apple"),

    new Fruit("pear"), new Fru it("cherry"), new Fruit("strawberry"), new Fruit("honeydew")) scala> theList.filter( { (f: Fruit) => f.isDelicious } ) res0: List[Fruit] = List(apple, cherry, strawberry) scala> theList.fold("The fruits on this list are: ")( { (stringSo Far: String, f: Fruit) => stringSoFar + " " + f.name } ) res1: String = "The fruits on this list are: apple pear cherry st rawberry honeydew" scala> theList.fold(0)( { (count: Int, f: Fruit) => count + " " + f.totalPieces } ) res2: Int = 42300 theList.reduce( { (f: Fruit) => f.totalPieces } ) res3: Int = 42300
  27. NESTED FOR-LOOPS def tryAllPairings(pies: List[Pie], iceCreams: List[IceCream]): List(Serving[Pie, IceCream]) {

    val servings = List[Serving[Pie,IceCream]]() for (p <- 0 until pies.length) { for (i <- 0 until iceCreams.length) { val serving = new Serving(p, i) serving :: servings } } servings } def tryAllPairings(pies: List[Pie], iceCreams: List[IceCream]): List(Serving[Pie, IceCream]) { pies.map( { (p: Pie) => iceCreams.map( { (i: IceCream) => new Serving(p, i) } ) } ) }
  28. IS THIS AN IMPROVEMENT? def tryAllPairings(pies: List[Pie], iceCreams: List[IceCream]): L

    ist(Serving[Pie, IceCream]) { val servingsLists = pies.map( { (p: Pie) => iceCreams.map( { (i: IceCream) => new Serving(p, i) } ) } ) servingsLists.flatten }
  29. FUNCTION COMPOSITION flatten compose map is flatMap, and it's MAGIC

    def bakeAPie(f: Fruit, c: Crust): Pie def eatAPie(p: Pie): HappyKelsey def bakeAndEatAPie(f: Fruit, c: Crust): HappyKelsey = eatAPie com pose bakeAPie //could also be written bakeAPie andThen eatAPie
  30. FUN WITH FOR-YIELD def goodPairings(pies: List[Pie], iceCreams: List[IceCream]): Li st(Serving[Pie,

    IceCream]) { for { p <- pies i <- iceCreams val serving = new Serving(p,i) if (serving.isGood) } yield { serving } } def pleaseEverybody(audience: List[Person], pies: List[Pie], ice Creams: List[IceCream]): List(ThankYou) { for { person <- audience p <- pies i <- iceCreams val serving = new Serving(p,i) if (serving.isGood) } yield { person.feed(serving) } }
  31. YUCK SCALA PROGRAMMING DOESN'T USE NULL. YIPPEE! public Serving<Pie, IceCream>

    serveBestALaMode(Pie key, Map<Pie, IceCream> pairings) { if(pairings != null) { IceCream iceCream = pairings.get(key); if(iceCream != null) { return new Serving(key, iceCream) } else { return null; } } }
  32. OPTION Option[T] is either a Some with a value of

    type T inside, or None representing nothing. val someOption: Option[String] = Some("this is a value") val noneOption: Option[String] = None val theSomeValue = someOption.get //returns "this is a value" val someIsDefined = someOption.isDefined //returns true val theNoneValue = noneOption.get //throws NoSuchElementException val someIsDefined = someOption.isDefined //returns false
  33. def serveBestALaMode(key: Pie, pairings: Map[Pie, IceCream]): Op tion[Serving[Pie,IceCream]] = {

    iceCream: Option[IceCream] = pairings.get(key); if (iceCream.isDefined) { Some(new Serving(key, iceCream.get)) } else { None } }
  34. OPTION IS KIND OF LIKE A COLLECTION .MAP .FLATMAP .FILTER

    someOption.map( {(str:String) => str + " SAN DIMAS HIGH SCHOOL FO OTBALL RULES"} ) //returns Some("this is a value SAN DIMAS HIGH SCHOOL FOO TBALL RULES") noneOption.map( {(str:String) => str + " SAN DIMAS HIGH SCHOOL FO OTBALL RULES"} ) //returns None val favoritePie: Option[Pie] = Some(rhubarb) favoritePie.map({ (pie: Pie) => pairings.get(pie) }) //returns Some(Some(butterPecan))--whoops! favoritePie.flatMap( { (pie: Pie) => pairings.get(pie) } ) //returns Some(butterPecan) val todaysSpecial: Option[Pie] val myOrder = todaysSpecial.filter( { (pie: Pie) => (pie != butte rPecan) }
  35. FOR-YIELD OVER OPTION for { pie <- todaysSpecial bestIceCream <-

    pairings.get(pie) iceCream <- availableFlavors.get(bestIceCream) } yield { myDessert } beautiful powerful tough to mess with
  36. "Let’s look at what it is that makes Thing a

    monad. The first thing is that I can wrap up a value inside of a new Thing...We have a function of type A => Thing; a function which takes some value and wraps it up inside a new Thing. We also have this fancy bind function, which digs inside our Thing and allows a function which we supply to use that value to create a new Thing. Scala calls this function “flatMap“.... What’s interesting here is the fact that bind is how you combine two things together in sequence. We start with one thing and use its value to compute a new thing." —Daniel Spiewak
  37. FLATMAP IS MAGIC flatMap hides our boilerplate. For Lists, it

    abstracts away a for-loop, letting us create a new List from an existing list. For Options, it abstracts away a null check, letting us create a new nullable value from an existing one. tough to mess with
  38. OTHER MONADS accumulating errors a cursor position in a database

    or file states in a state machine an environment that changes powerful
  39. READABILITY “Is Clojure code hard to understand? Imagine if every

    time you read Java source code and encountered syntax elements like if statements, for loops, and anonymous classes, you had to pause and puzzle over what they mean. There are certain things that must be obvious to a person who wants to be a productive Java developer. Likewise there are parts of Clojure syntax that must be obvious for one to efficiently read and understand code. Examples include being comfortable with the use of let, apply, map, filter, reduce and anonymous functions...”—R. Mark Volkmann
  40. DSLS from specs2 class HelloWorldSpec extends Specification { "The 'Hello

    world' string" should { "contain 11 characters" in { "Hello world" must have size(11) } "start with 'Hello'" in { "Hello world" must startWith("Hello") } "end with 'world'" in { "Hello world" must endWith("world") } } }
  41. PURITY “The truth is that good programmers mix the styles

    quite a bit. We program imperatively when needed, and functionally when possible.” - Michael O. Church