Upload
jason-larsen
View
444
Download
0
Embed Size (px)
DESCRIPTION
Functional programming tips in swift.
Citation preview
7 Habits For a More Functional Swift
Jason Larsen
@jarsen
7 Habits1. Avoid mutability
2. Avoid for-loops
3. Combine map/filter/reduce
4. Be lazy
5. Curry functions
6. Write DSLs
7. Stop objectifying code
What is a Function?
f(x) = x * x
Functions Are Mappings4 Do not mutate input
4 Do not change external state
4 Determined only by explicit inputs
Consequences of Pure Functions4 Return the same values every time for input
4 No Side Effects
4 Purity allows laziness (since the value will be the same whenever its computed, we can compute it only when we need it)
4 Concurrency is easy, b/c no shared state
Consequences of Pure Functions4 No I/O (user input, printing, random values, etc)
4 No state
4 No variables (writing to variables is a side effect)
4 No Side Effects
Is Swift Functional?
#1
Let it be
Bad// find the bug, and don't tell me you've never done thisfunc square(x: Int) -> Int { return x * x}
var a = [1,2,3,4,5]var b = [Int]()
for x in a { a.append(square(x))}
Goodfunc square(x: Int) -> Int { return x * x}
let a = [1,2,3,4,5]let b = a.map({x in square(x)})
Beautifulfunc square(x: Int) -> Int { return x * x}
let a = [1,2,3,4,5]let b = a.map(square)
Immutable structsstruct Person { let name: String let age: Int}
let alice = Person(name: "Alice", age: 22)let alice2 = Person(name: alice.name, age: 23) // transform data
Transforming Immutable Objectsextension Dictionary { func dictionaryByUpdatingKey(key: Key, value: Value) -> Dictionary { var mutable = self mutable.updateValue(value, forKey: key) return mutable }}
let animalNoiseMap = ["cow" : "moo", "cat" : "meow"]let animalNoiseMapImproved = animalNoiseMap.dictionaryByUpdatingKey("dog", value: "woof")
Transforming Immutable Objectsstruct Person { let name: String let age: Int
func age(age: Int) -> Person { return Person(name: self.name, age: age) }}
let bob = Person(name: "Bob", age: 25)let birthdayBob = bob.age(bob.age + 1)
Transforming Immutable Objectsstruct Person { let name: String let age: Int
static func age(person: Person, age: Int) -> Person { return Person(name: person.name, age: age) }}
let bob = Person(name: "Bob", age: 25)let birthdayBob = Person.age(bob, age: bob.age + 1)
Transforming Immutable Objectsclass Person { let name: String let age: Int
init(name: String, age: Int) { self.name = name self.age = age }
init(person: Person, name: String? = nil, age: Int? = nil) { self.name = name ?? person.name self.age = age ?? person.age }}
let bob = Person(name: "Bob", age: 25)let birthdayBob = Person(person: bob, age: bob.age + 1)
#2
for the love of loops!
MapMap each item in an existing collection to something else.
FilterFind objects in a collection that match your criteria by filtering out everything that doesn't match.
Uglyvar bestStudents = [Student]()
for student in students { if (student.grade > 90) { bestStudents.append(student) }}
Beautifullet bestStudents = students.filter { $0.grade > 90 }
Also Beautifulfunc isBestStudent(student: Student) -> Bool { return student.grade > 90}
let bestStudents = students.filter(isBestStudent)
ReduceReduces all sequence elements into one value. Takes an initial value, passes that value through as an accumulator, which may be updated in each iteration.
Uglylet a = [1,2,3,4,5]
var sum = 0for x in a { sum += x}
Calculating Sums With Reducelet a = [1,2,3,4]let sum = a.reduce(0, combine: { (accumulator, value) in return accumulator + value})
Calculating Sums With Reducelet a = [1,2,3,4]let sum = a.reduce(0, combine: { (accumulator, value) in accumulator + value })
Calculating Sums With Reducelet a = [1,2,3,4]let sum = a.reduce(0, combine: { $0 + $1 })
Calculating Sums With Reducelet a = [1,2,3,4]let sum = a.reduce(0, +)
Finding the Max Value With Reducelet numbers = [1,4,15,23,9]
if let initial = numbers.first { let numMax = numbers.reduce(initial) { (m, x) in return x > m ? x : m }}
Finding the Max Value With Reducelet numbers = [1,4,15,23,9]
if let initial = numbers.first { let numberMax = numbers.reduce(initial) { (m, x) in return max(m, x) }}
Finding the Max Value With Reducelet numbers = [1,4,15,23,9]
if let initial = numbers.first { let numberMax = numbers.reduce(initial, max)}
Counting Frequencies with Reducelet numbers = [1,4,15,23,1,1,9,9,23,9]
let histogram = numbers.reduce([Int: Int]()) { (acc, x) in if let count = acc[x] { return acc.dictionaryByUpdatingKey(x, value: count + 1) } else { return acc.dictionaryByUpdatingKey(x, value: 1) }}
Composing Filterstypealias Filter = CIImage -> CIImage
let filters: [Filter] = [colorOverlay, blur, drawTitle]let filteredImage = filters.reduce(image, combine: { $1($0) } )
#3
By our powers combined
struct Person { let name: String let age: UInt}
let people = [Person(name: "Alice", age: 22), Person(name: "Bob", age: 23), Person(name: "Mallory", age: 25)]let ageSum = people.map({$0.age}).reduce(0, combine: +)
let people = [Person(name: "Alice", age: 22), Person(name: "Bob", age: 23), Person(name: "Mallory", age: 25)]let namesBeforeJason = people.map({$0.name}).filter { name in name.compare("Jason") == NSComparisonResult.OrderedAscending}
Zip it uplet a = Array(1...5)let b = Array(6...10)let result = map(Zip2(a,b), +) // [7, 9, 11, 13, 15]
#4
Be Lazy
class EvenNaturalNumbers: SequenceType { typealias GeneratorType = EvenNaturalNumbersGenerator
func generate() -> EvenNaturalNumbersGenerator { return EvenNaturalNumbersGenerator() }}
class EvenNaturalNumbersGenerator : GeneratorType { var current = 2
typealias Element = Int
func next() -> Int? { let ret = current current += 2 return ret }}
class Fibonacci : SequenceType { typealias GeneratorType = FibonacciGenerator
func generate() -> FibonacciGenerator { return FibonacciGenerator() }}
class FibonacciGenerator : GeneratorType { var current = 0, nextValue = 1
typealias Element = Int
func next() -> Int? { let ret = current current = nextValue nextValue = nextValue + ret return ret }}
func take<T, S : SequenceType where S.Generator.Element == T>(n: Int, sequence: S) -> [T] { var gen = sequence.generate() var values = [T]() for _ in (1...n) { if let value = gen.next() { values.append(value) } } return values}
take(5, [1,2,5,12,31,4,2])take(10, EvenNaturalNumbers())take(10, Fibonacci())
func filter<S : SequenceType>(source: S, includeElement: (S.Generator.Element) -> Bool) -> [S.Generator.Element]
func map<S : SequenceType, T> (source: S, transform: (S.Generator.Element) -> T) -> [T]
#5
Curried Functions. Yum.
func addNormal(x:Int, y : Int) -> Int { return x + y}
let sum = addNormal(1, 2)
func addCurried(x:Int) -> Int -> Int { return {y in return x + y}}
let sum = addCurried(1)(2)
let numbers = Array(0...5)let numbersIncrementedBy1 = numbers.map(addCurried(1))let numbersIncrementedBy2 = numbers.map(addCurried(2))
// taken from the excellent WIP "Functional Programming in Swift"// http://www.objc.io/books/
typealias Filter = CIImage -> CIImage
func blur(radius: Double) -> Filter { return { image in let parameters : Parameters = [kCIInputRadiusKey: radius, kCIInputImageKey: image] let filter = CIFilter(name:"CIGaussianBlur", parameters:parameters) return filter.outputImage }}
let blurredImage = blur(2.0)(image)
Instance Methods are Curriedclass BankAccount { var balance: Double = 0.0
func deposit(amount: Double) { balance += amount }}
let account = BankAccount()account.deposit(100) // balance is now 100
let depositor = BankAccount.depositdepositor(account)(100) // balance is now 200
#6
Domain-Specific Langauges
Custom Flow Controlfunc unless(condition: Bool, then: () -> ()) { if (!condition) { then() }}
unless(1 != 1) { println("Phew. Identity holds.")}
Cucumber-Style BDD Frameworkgiven("I have entered (.*) into the calculator") { n in let calculator = Calculator() calculator.push(n)}
Sinatra-Style Web FrameworkGET("/greetings/:name") { request, params in let name = params["name"] ?? "Anonymous" let greeting = "<h1>Hello, \(name)!</h1>" return Response(body: greeting, code: 200)}
Custom Operatorsinfix operator |> { associativity left }func |> (filter1: Filter, filter2: Filter) -> Filter { return {img in filter1(filter2(img))}}
let myFilter = blur(blurRadius) |> colorOverlay(overlayColor)let result = myFilter(image)
#7
Stop Objectifying Code
Objectification4 Class - Noun
4 Properties - Nouns related to noun above
4 Instance Methods - Actions instance of Class can perform
4 Class Methods - Actions related to Class in general
Functionalization4 Data
4 Functions transform data
Thinking About Data
Arraysgreat for variable length data of the same type
4 list of students in a class
4 lines in a document
4 search results in a JSON response
Tuples / Named Tuplesfixed length list. can hold mixed types, but probably best to prefer same types
4 Points/Vectors
4 functions with multiple return values
typealias Vector2D = (x: Double, y: Double)let foo = Vector2D(2, 4)
DictionariesDictionaries are maps.
4 anything that needs to be mapped to something else
4 a JSON response
StructsEncapsulate properties of multiple types. No inheritance. Free constructor for all the immutable properties.
4 Anything you might use a tuple for
4 Data related to a student - grade, first name, last name
EnumsAnytime something has a set of options
4 HTTP Methods
4 Errors
4 Optionals
ClassesObjects. Object Oriented Programming. Not a bad thing, but not terribly functional.
Typealias All The Thingstypealias Filter = Request->Requesttypealias Handler = (Request,Parameters)->Responsetypealias Model = [String : [String]]typealias Renderer = Model -> Stringtypealias Parameters = [String: String]
Resources4 http://www.drewag.me/posts/practical-use-for-curried-functions-in-swift
4 https://www.skillsmatter.com/skillscasts/5678-an-introduction-to-haskell
4 http://matt.might.net/articles/implementing-laziness
4 http://www.scottlogic.com/blog/2014/06/26/swift-sequences.html
4 http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/