Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
7 Habits For a More Functional Swift
Jason Larsen 
@jarsen
7 Habits 
1. 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 Mappings 
4 Do not mutate input 
4 Do not change external state 
4 Determined only by explicit inputs
Consequences of Pure Functions 
4 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 Functions 
4 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 this 
func 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)) 
}
Good 
func square(x: Int) -> Int { 
return x * x 
} 
let a = [1,2,3,4,5] 
let b = a.map({x in square(x)})
Beautiful 
func square(x: Int) -> Int { 
return x * x 
} 
let a = [1,2,3,4,5] 
let b = a.map(square)
Immutable structs 
struct 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 Objects 
extension 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 Objects 
struct 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 Objects 
struct 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 Objects 
class 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!
Map 
Map each item in an existing collection to something 
else.
Filter 
Find objects in a collection that match your criteria by 
filtering out everything that doesn't match.
Ugly 
var bestStudents = [Student]() 
for student in students { 
if (student.grade > 90) { 
bestStudents.append(student) 
} 
}
Beautiful 
let bestStudents = students.filter { $0.grade > 90 }
Also Beautiful 
func isBestStudent(student: Student) -> Bool { 
return student.grade > 90 
} 
let bestStudents = students.filter(isBestStudent)
Reduce 
Reduces all sequence elements into one value. Takes an 
initial value, passes that value through as an 
accumulator, which may be updated in each iteration.
Ugly 
let a = [1,2,3,4,5] 
var sum = 0 
for x in a { 
sum += x 
}
Calculating Sums With Reduce 
let a = [1,2,3,4] 
let sum = a.reduce(0, combine: { (accumulator, value) in 
return accumulator + value 
})
Calculating Sums With Reduce 
let a = [1,2,3,4] 
let sum = a.reduce(0, combine: { (accumulator, value) in accumulator + value })
Calculating Sums With Reduce 
let a = [1,2,3,4] 
let sum = a.reduce(0, combine: { $0 + $1 })
Calculating Sums With Reduce 
let a = [1,2,3,4] 
let sum = a.reduce(0, +)
Finding the Max Value With Reduce 
let 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 Reduce 
let 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 Reduce 
let numbers = [1,4,15,23,9] 
if let initial = numbers.first { 
let numberMax = numbers.reduce(initial, max) 
}
Counting Frequencies with Reduce 
let 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 Filters 
typealias 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 up 
let 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 Curried 
class 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.deposit 
depositor(account)(100) // balance is now 200
#6 
Domain-Specific Langauges
Custom Flow Control 
func unless(condition: Bool, then: () -> ()) { 
if (!condition) { 
then() 
} 
} 
unless(1 != 1) { 
println("Phew. Identity holds.") 
}
Cucumber-Style BDD Framework 
given("I have entered (.*) into the calculator") { n in 
let calculator = Calculator() 
calculator.push(n) 
}
Sinatra-Style Web Framework 
GET("/greetings/:name") { request, params in 
let name = params["name"] ?? "Anonymous" 
let greeting = "<h1>Hello, (name)!</h1>" 
return Response(body: greeting, code: 200) 
}
Custom Operators 
infix 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
Objectification 
4 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
Functionalization 
4 Data 
4 Functions transform data
Thinking About Data
Arrays 
great 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 Tuples 
fixed 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)
Dictionaries 
Dictionaries are maps. 
4 anything that needs to be mapped to something else 
4 a JSON response
Structs 
Encapsulate 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
Enums 
Anytime something has a set of options 
4 HTTP Methods 
4 Errors 
4 Optionals
Classes 
Objects. Object Oriented Programming. Not a bad thing, 
but not terribly functional.
Typealias All The Things 
typealias Filter = Request->Request 
typealias Handler = (Request,Parameters)->Response 
typealias Model = [String : [String]] 
typealias Renderer = Model -> String 
typealias Parameters = [String: String]
Resources 
4 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

More Related Content

7 Habits For a More Functional Swift

  • 1. 7 Habits For a More Functional Swift
  • 3. 7 Habits 1. Avoid mutability 2. Avoid for-loops 3. Combine map/filter/reduce 4. Be lazy 5. Curry functions 6. Write DSLs 7. Stop objectifying code
  • 4. What is a Function? f(x) = x * x
  • 5. Functions Are Mappings 4 Do not mutate input 4 Do not change external state 4 Determined only by explicit inputs
  • 6. Consequences of Pure Functions 4 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
  • 7. Consequences of Pure Functions 4 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
  • 10. Bad // find the bug, and don't tell me you've never done this func 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)) }
  • 11. Good func square(x: Int) -> Int { return x * x } let a = [1,2,3,4,5] let b = a.map({x in square(x)})
  • 12. Beautiful func square(x: Int) -> Int { return x * x } let a = [1,2,3,4,5] let b = a.map(square)
  • 13. Immutable structs struct Person { let name: String let age: Int } let alice = Person(name: "Alice", age: 22) let alice2 = Person(name: alice.name, age: 23) // transform data
  • 14. Transforming Immutable Objects extension 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")
  • 15. Transforming Immutable Objects struct 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)
  • 16. Transforming Immutable Objects struct 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)
  • 17. Transforming Immutable Objects class 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)
  • 18. #2 for the love of loops!
  • 19. Map Map each item in an existing collection to something else.
  • 20. Filter Find objects in a collection that match your criteria by filtering out everything that doesn't match.
  • 21. Ugly var bestStudents = [Student]() for student in students { if (student.grade > 90) { bestStudents.append(student) } }
  • 22. Beautiful let bestStudents = students.filter { $0.grade > 90 }
  • 23. Also Beautiful func isBestStudent(student: Student) -> Bool { return student.grade > 90 } let bestStudents = students.filter(isBestStudent)
  • 24. Reduce Reduces all sequence elements into one value. Takes an initial value, passes that value through as an accumulator, which may be updated in each iteration.
  • 25. Ugly let a = [1,2,3,4,5] var sum = 0 for x in a { sum += x }
  • 26. Calculating Sums With Reduce let a = [1,2,3,4] let sum = a.reduce(0, combine: { (accumulator, value) in return accumulator + value })
  • 27. Calculating Sums With Reduce let a = [1,2,3,4] let sum = a.reduce(0, combine: { (accumulator, value) in accumulator + value })
  • 28. Calculating Sums With Reduce let a = [1,2,3,4] let sum = a.reduce(0, combine: { $0 + $1 })
  • 29. Calculating Sums With Reduce let a = [1,2,3,4] let sum = a.reduce(0, +)
  • 30. Finding the Max Value With Reduce let numbers = [1,4,15,23,9] if let initial = numbers.first { let numMax = numbers.reduce(initial) { (m, x) in return x > m ? x : m } }
  • 31. Finding the Max Value With Reduce let numbers = [1,4,15,23,9] if let initial = numbers.first { let numberMax = numbers.reduce(initial) { (m, x) in return max(m, x) } }
  • 32. Finding the Max Value With Reduce let numbers = [1,4,15,23,9] if let initial = numbers.first { let numberMax = numbers.reduce(initial, max) }
  • 33. Counting Frequencies with Reduce let 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) } }
  • 34. Composing Filters typealias Filter = CIImage -> CIImage let filters: [Filter] = [colorOverlay, blur, drawTitle] let filteredImage = filters.reduce(image, combine: { $1($0) } )
  • 35. #3 By our powers combined
  • 36. 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: +)
  • 37. 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 }
  • 38. Zip it up let a = Array(1...5) let b = Array(6...10) let result = map(Zip2(a,b), +) // [7, 9, 11, 13, 15]
  • 40. 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 } }
  • 41. 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 } }
  • 42. 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())
  • 43. 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]
  • 45. func addNormal(x:Int, y : Int) -> Int { return x + y } let sum = addNormal(1, 2)
  • 46. func addCurried(x:Int) -> Int -> Int { return {y in return x + y} } let sum = addCurried(1)(2)
  • 47. let numbers = Array(0...5) let numbersIncrementedBy1 = numbers.map(addCurried(1)) let numbersIncrementedBy2 = numbers.map(addCurried(2))
  • 48. // 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)
  • 49. Instance Methods are Curried class 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.deposit depositor(account)(100) // balance is now 200
  • 51. Custom Flow Control func unless(condition: Bool, then: () -> ()) { if (!condition) { then() } } unless(1 != 1) { println("Phew. Identity holds.") }
  • 52. Cucumber-Style BDD Framework given("I have entered (.*) into the calculator") { n in let calculator = Calculator() calculator.push(n) }
  • 53. Sinatra-Style Web Framework GET("/greetings/:name") { request, params in let name = params["name"] ?? "Anonymous" let greeting = "<h1>Hello, (name)!</h1>" return Response(body: greeting, code: 200) }
  • 54. Custom Operators infix 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)
  • 56. Objectification 4 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
  • 57. Functionalization 4 Data 4 Functions transform data
  • 59. Arrays great 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
  • 60. Tuples / Named Tuples fixed 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)
  • 61. Dictionaries Dictionaries are maps. 4 anything that needs to be mapped to something else 4 a JSON response
  • 62. Structs Encapsulate 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
  • 63. Enums Anytime something has a set of options 4 HTTP Methods 4 Errors 4 Optionals
  • 64. Classes Objects. Object Oriented Programming. Not a bad thing, but not terribly functional.
  • 65. Typealias All The Things typealias Filter = Request->Request typealias Handler = (Request,Parameters)->Response typealias Model = [String : [String]] typealias Renderer = Model -> String typealias Parameters = [String: String]
  • 66. Resources 4 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