Learning FunctionalProgramming without Growing a Neckbeard – Data is immutable.



Learning FunctionalProgramming without Growing a Neckbeard – Data is immutable.

0 0


fpintrotalk

Learning Functional Programming Without Growing a Neckbeard

On Github kelseyq / fpintrotalk

Learning FunctionalProgramming without Growing a Neckbeard

by Kelsey Gilmore-Innis / @kelseyinnis

Hi!

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 Coursera

Why are you here?

To learn to write code with drive that don't take no jive.

The Pam Grier Criteria for Code Badassery

Code, like Pam Grier, should be:

  • Powerful,
  • Beautiful, and
  • Tough to mess with.

Powerful

  • Do big things
  • Do them easily
  • Do them quickly

Tough to mess with

  • Secure from outside manipulation
  • Hard to unintentionally make mistakes
  • Easy to maintain
Another word for this (in terms of code) is "robust", but I felt weird calling Pam Grier "robust"

Beautiful

  • Elegant, concise, and--yes--readable
  • Able to be easily reused
  • Fun to write
I DO NOT want to objectify Ms. Grier! Functionify her instead! First two attributes contribute to & create her beauty/appeal, which is a decent analogy with Scala too

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
    • some things you'll notice technically, you can use return, but it's a code smell--with functional code, it's hard to know where you're returning to static typing: variables have types. if a variable is of type string, the value assigned to it needs to be a string, and this is checked at compile time type inference: when it's reasonable, the computer figures out what type you mean it compiles down to java so you can write really javalike code, and many of us do to start

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", "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

But what does it look like?

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

Functional Programming

What is functional programming?

Well, what is a program?

Ask A Kid

(or Simple English Wikipedia)

A computer program is a list of instructions that tell a computer what to do.
WRONG
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

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.
If a program's not a series of commands, then what is it?
Functional programming is a style of programming that emphasizes the evaluation of expressions rather than the execution of commands.—comp.lang.functional FAQ what things are, not what to do. you're probably thinking two things: 1. i tell the computer what things are all the time. this is some huge fancy programming technique? 2. if i already knew what all the things were, i probably wouldn't need to write a computer program!

What things are

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

What is a function?

A function is a relation between values where each of its input values gives back exactly one output value, playa.
hey thanks e40! so, this is the mathematical definition both a definition and a rule...well, a guideline stricter than the usual programming definition, which is usually just a subroutine that takes some or no parameters and returns a value still covers a lot of things you know and love you might know this as a "static method", which means it doesn't require an instance of a specific class OR that class's data to run

Some Functions

  • Math.sqrt(x)
  • Collections.max(list)
  • Arrays.copyOf(original, newLength)
  • String.valueOf(anInt)
mention here that when we said that each input gave only one output, we didn't mean you could only put in one input value. we consider the input as a whole, so for each set of input values, there's only one output.

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 illustration is how i learned it in math class. notice the box is closed--nothing goes into it at any point after the input why? well, why would you put something in it? to change the output, which means our definition would fail, cause you could get 2 diff outputs for same input and if you change something outside of the box, what if another function is using that value as an input? you've broken their definition of a function

This is our building block.

Sooooo....

so, we've taken away some of our abilities. what do we get in return?

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
correctness: you can reason about it eliminates an entire category/vector of bugs unit tests: you don't have to set up state debugging can be easier to eliminate things. not easier in general!

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
rest of the quote: "an unbounded amount of intermediate stuff can be shoved into an imperative program, with no change to its interface" readability: you can tell what's going to be affected by looking at the interface. interfaces can become complicated, but that encourages you to break the code down into smaller bits smaller bits encourage reuse changes have fewer unseen consequences

Functions are commutative.

val firstThing = doOneThing()
val secondThing = doAnotherThing()

val thirdThing = doTheLastThing(firstThing, secondThing)
						
  • parallelization
  • concurrency
  • lazy evaluation
powerful
fancy way of saying order doesn't matter do we have to do the first two lines in the same order? do the same computers have to do them? when do they have to be done? obviously sometimes order does matter, but we have ways of specifying that

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...
String s1 = "san dimas high school football rules"
String s2 = s1.toUpperCase

println("string 1: " + s1);
println("string 2: " + s2);
							
this is because if you don't, an object could be changed while another function is in the middle of using it val is a value, like adding final in java scala has both immutable and mutable data structures, but defaults to immutable
  • concurrency
  • rollback of data
  • simplicity
powerful
tough to mess with
inherently threadsafe robust--many many bugs come from threads sharing mutable data data structures are easier to implement performance (only if someone asks!): doesn’t actually copy data, it copies references to the old data. example: linked list
  • Functions are deterministic
  • Functions are encapsulated
  • Functions are commutative
  • Data is immutable

Let's build.

...how?

need to know how to snap them together

functions as first class citizens

one way has to do with how functions are treated in scala functions are first class objects in scala

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)
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 value

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 +">" + text + "" }
val tagWithAndSpam = tagText2("andSpam")
val breakfast = tagWithAndSpam("Spam Bacon and Sausage")
	//result: <andSpam>Spam Bacon and Sausage</andSpam> 
beautiful
powerful
* use the words “partially applied” * we’re using a function to return another function * beautiful: much more readable * powerful: really useful in writing libraries. you can provide your users with a framework and let them specialize * you count as a consumer of your own code! reusable * side note: could use this technique to break any function with multiple arguments into a chained series of single argument functions--Haskell does this

Currying

yum!

* currying, after Haskell Curry * recap: assigned as variables, put into data structure, returned from function. now: passing as arguments

Higher-order functions

I didn't make this--it was created by a professor at Willamette university as a proposed design for Haskell shirts functions that takes functions are called higher-order functions the meat of functional programming

For Loop

Java
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

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)
	}
}
what is always the same? always writing that outer wrapper loop if you could abstract it into a function, what would that look like?

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)
	}
}
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

This:

abstract class Collection[A] {
	...
	def foreach(theFunction: (A) => Unit): Unit = {
	 	for (i <- 0 until this.length) {
			theFunction(this(i))
		}	
	}
	...
}

is NOT how Scala implemented foreach

tough to mess with
powerful
actual implementation does all sorts of cool functional tricks we don't need to know anything about it though! it can get better without us changing things, and we can't make dumb mistakes cool balance--we get to configure what we want (the passed in function) while not caring about the mechanics of the iteration itself

Something a little juicier

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

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
you don't have to declare the intermediate value if you're not going to use it again don't have to worry about that intermediate value getting changed somehow obviously ridiculous here this is still a oneliner, which makes it easy to pass into other functions anonymously--can build some cool stuff want to abstract a little more--like if rhubarb comes into season go back to last slide and compare more concise, more readable--right to left less variables to keep track of

Collection handling

filter

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)

fold

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"

reduce

scala> theList.fold(0)( { (count: Int, f: Fruit) => count + " " + f.totalPieces } )
res2: Int = 42300

theList.reduce( { (f: Fruit) => f.totalPieces } )
res3: Int = 42300
combination of map & reduce = MapReduce, used by google to do massively huge data processing (and by a bunch of others as well) fold & reduce are actually called foldLeft & reduceLeft, and have corresponding Right methods

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)
		} )
	} )	
}
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

Is This an Improvement?

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

function composition

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
flatten compose map is flatMap, and it's MAGIC
two functions where you often end up passing the result of one to the other abstract this away--it's called function composition we want to turn them into one function turns out flatten(m map f) is flatMap, and it turns out flatMap is magic go back and edit pie method better, but still not exactly gorgeous

For-Yield

 def tryAllPairings(pies: List[Pie], iceCreams: List[IceCream]): List(Serving[Pie, IceCream]) {
	for {
		p <- pies
		i <- iceCreams
	} yield {
		new Serving(p,i)
	}
}
beautiful!
what does this look like it does? syntactic sugar for a nested for loop look back at original version -- this gets Pam's stamp of approval

Fun With For-Yield

 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
  • partial application
  • higher order functions
  • function composition
  • for-yield
  • partial application--andSpam, html tag
  • higher order functions--fruit, and eventually pie
  • function composition--added ice cream
  • for-yield--served it to you

and now for something (not really) completely different

null.

what is null? what can be null? what happens if you reference a null object? what kind of code does this lead to?

yuck

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;
        }
    }
}

Scala programming doesn't use null. Yippee!

big ups to my coworker Doug for this example we have to make sure each of these things aren't null before we access them so let's rewrite this--wait a second...what if we ask for a key tht isn't in there? a cow pie?

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
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?

Option is kind of like a Collection

.map

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

.flatMap

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)

.filter

val todaysSpecial: Option[Pie]
val myOrder = todaysSpecial.filter( { (pie: Pie) => (pie != butterPecan) }
.map takes a function. that function takes a type of whatever the value inside the option is. whatever type that function returns, .map will return an option of that type .flatMap takes a function. that function takes a type of whatever the value inside the option is. whatever type that function returns, .map will return an option of that type filter function returns a bool. if the value doesn't satisfy the predicate, the option returned is none

for-yield over Option

 for {
	pie <- todaysSpecial
	bestIceCream <- pairings.get(pie)
	iceCream <- availableFlavors.get(bestIceCream) } yield {
	myDessert
}
beautiful
powerful
tough to mess with
you picky huh? point out that value on left is inner value if any of these is none, the result is none we know for-yield is beautiful and powerful, but this is particularly tough to mess with--really readable and you know you're getting the right value general pattern--validating a series of options before passing them to each other--is super common for yield took care of nested for loops and also nested if statements

Option is a monad

What is a monad?

eeeee! famously scary aspect of functional programming. lots of tutorials, weird metaphors

"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 option

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
reduces point of failure. lets that boilerplate code be optimized

other monads

  • accumulating errors
  • a cursor position in a database or file
  • states in a state machine
  • an environment that changes
powerful
last one is how we handle side effects in functional code

extra credit whoa

a semicolon is a monad

  • partial application
  • higher order functions
  • function composition
  • for-yield
  • monads

The Psychology of Functional Programming

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

DSLs

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

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

Thank you