7 habits for a more functional swift

Post on 01-Dec-2014

443 Views

Category:

Mobile

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Functional programming tips in swift.

TRANSCRIPT

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/

top related