On Github kelseyq / fpintrotalk
I'm Kelsey.
I work at Reverb writing code that something something. I use Scala to do that.
Who are you? What language do you mainly use, how long have you been coding, ask about CourseraTo learn to write code with drive that don't take no jive.
Code, like Pam Grier, should be:
val x: String = "a" val y = 2 val z = 15.3 val myThing = new Thing val theList = List("this", "is", "a", "list") val whatIsIt = theList(4)ask audience what each type is they're smart enough to figure it out, so is the compiler
def haggle(price: Int, offer: Int): String = { val theResponse: String = if (price <= offer + 5) { "You've got a deal!" } else { "I definitely wouldn't pay more than " + (offer + (price - offer)/2) + " for it." } theResponse }point out that types are defined after the variable name return types are after the argument list we don't have to specifically say "return" take off theResponse, it still works take off String, it still works
Well, what is a program?
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
not wrong but only one kind of programming object oriented is a kind of imperative imperative: you're saying "Do this, then do that, then do the other thing." oo: this is a doohickey that can do this, and it can talk to a whatzit that can do that. now have your doohick do this and then your whatzit do that.val theQueen = "Elizabeth II"
but also...
def theGovernor(state: State) = { val candidates = state.getCandidates candidates(getTopVoteGetter) }WAY better example needed!!!!--Tarantino movie? read it out loud first one is assignment second one is a function, and it's the critical one our building block for functional programming
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)
This is our building block.
Sooooo....
You will always get the same result if you run them with the same data.
val firstThing = doOneThing() val secondThing = doAnotherThing() val thirdThing = doTheLastThing(firstThing, secondThing)
Once an object is created, it cannot be changed.
If you need to change an object, make your own copy.
String s1 = "san dimas high school football rules" String s2 = s1.toUpperCase println("string 1: " + s1); println("string 2: " + s2);
Let's build.
...how?
need to know how to snap them togetherval 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)can be assigned to variables, stored in data structures, returned as values, and passed to functions
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"
addSpam's type is (String) => String
(list of parameters' types) => return type
add brackets around function to make it easier to see explain the type signature (it's confusing!) we've assigned to variables & stored in data structures. last bit is kind of interesting but probably not very useful so, let's return a function as a valuedef 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 +">" + text + "" } val tagWithAndSpam = tagText2("andSpam") val breakfast = tagWithAndSpam("Spam Bacon and Sausage") //result: <andSpam>Spam Bacon and Sausage</andSpam>
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]; } }Scala
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); } }walk through intention of code in Java (moronic) they're pretty much the same. there is a better way! how many times have you written something like this? this is boilerplate to the extreme
a function that takes a list and a function
(list of parameters' types) => return type foreach(fruitList:List(fruits), theFunction: (Fruit) => Unit): Unitdef 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) } }what is always the same? always writing that outer wrapper loop if you could abstract it into a function, what would that look like?
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) } }make it generic? further! what if we could call for each on a bunch of different kinds of collections? you could get more generic with your type signatures, or define on parent class this is what scala did. how you'd actually write this moronic code in scala
abstract class Collection[A] { ... def foreach(theFunction: (A) => Unit): Unit = { for (i <- 0 until this.length) { theFunction(this(i)) } } ... }
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 }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")) val makePie = { (f: Fruit) => new Pie(f) } fruits.map(makePie) }explain list concatenation point out var this is another common operation: do something to each member of a list data is immutable though! so we'll want to return a new list of new objects no matter what
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) } )
val theList = List(new Fruit("apple"), new Fruit("pear"), new Fruit("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: ")( { (stringSoFar: String, f: Fruit) => stringSoFar + " " + f.name } ) res1: String = "The fruits on this list are: apple pear cherry strawberry honeydew"
scala> theList.fold(0)( { (count: Int, f: Fruit) => count + " " + f.totalPieces } ) res2: Int = 42300 theList.reduce( { (f: Fruit) => f.totalPieces } ) res3: Int = 42300
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) } ) } ) }walk over nested for loop use printed notes to edit code into functional style not bad one little problem: it doesn't compile. list of lists - explain why use flatten
def tryAllPairings(pies: List[Pie], iceCreams: List[IceCream]): List(Serving[Pie, IceCream]) { val servingsLists = pies.map( { (p: Pie) => iceCreams.map( { (i: IceCream) => new Serving(p, i) } ) } ) servingsLists.flatten }is this better? pam's not sure, and neither am i might be more powerful, but it's really not very pretty just as much boilerplate as the imperative style what if we want to add another loop? make each person in here a plate with each pie serving? have to map and then flatten over this whole thing again
def bakeAPie(f: Fruit, c: Crust): Pie def eatAPie(p: Pie): HappyKelsey def bakeAndEatAPie(f: Fruit, c: Crust): HappyKelsey = eatAPie compose bakeAPie //could also be written bakeAPie andThen eatAPie
def tryAllPairings(pies: List[Pie], iceCreams: List[IceCream]): List(Serving[Pie, IceCream]) { for { p <- pies i <- iceCreams } yield { new Serving(p,i) } }
def goodPairings(pies: List[Pie], iceCreams: List[IceCream]): List(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], iceCreams: List[IceCream]): List(ThankYou) { for { person <- audience p <- pies i <- iceCreams val serving = new Serving(p,i) if (serving.isGood) } yield { person.feed(serving) } }very easily extensible, and readable
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; } } }
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 falsesometimes you need a value that means nothing. a key without a value in a map is one example. opening a file that doesn't exist is another. sometimes values are just optional-- 4 digit zip code suffix, or second address line one of the things that's nice about this is that it lets you know which code could be null. if it isn't in an option, you don't have to check it (unless it came from java!!!)(
def serveBestALaMode(key: Pie, pairings: Map[Pie, IceCream]): Option[Serving[Pie,IceCream]] = { iceCream: Option[IceCream] = pairings.get(key); if (iceCream.isDefined) { Some(new Serving(key, iceCream.get)) } else { None } }so, again, pam isn't sure this isn't really any better. you still have to do this isDefined check everywhere, and if you mess up .get will throw an exception and by the way what does this have to do with functional programming?
someOption.map( {(str:String) => str + " SAN DIMAS HIGH SCHOOL FOOTBALL RULES"} ) //returns Some("this is a value SAN DIMAS HIGH SCHOOL FOOTBALL RULES") noneOption.map( {(str:String) => str + " SAN DIMAS HIGH SCHOOL FOOTBALL 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 != butterPecan) }
for { pie <- todaysSpecial bestIceCream <- pairings.get(pie) iceCream <- availableFlavors.get(bestIceCream) } yield { myDessert }
"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 walk through this quote with list then with optionflatMap 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.
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") } } }from specs2 less boilerplate and type inference mean closer to english really expressive what's even cooler is that it's extremely extensible type signatures can get complicated, but you don't need to muck around in them for them to work