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 worksWell, 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): 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)
}
}
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 scalaabstract 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 whatval 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 againdef 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 false
sometimes 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