Programação Funcional na
Prática com Swift
Rodrigo Amorim Ruiz
Vikki (vikki.ai)
Porque Swift
• Passagem por valor
• Tipagem estática + inferência
• Optional
• Lambda e outras construções de FP
• Enum
VS
Programação Orientada a Objetos
Programação Funcional
Comparação
Considerações
• Swift vs Cocoa
• Questão de estilo
• OO / Procedural / Imperativo
• Técnicas de FP em OO
• Performance
• Leitura vs Escrita
• Melhor ferramenta para cada problema
Programação Funcional Programação Orientada a ObjetosVS
Programação Funcional Programação Orientada a ObjetosVS
Motivos
Class vs Struct
// OO
func customPrint(person: Person) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: {
print("After 10 seconds: (person.name)")
})
}
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let p = Person(name: "Rodrigo")
customPrint(person: p)
p.name = "Thomas"
print(p.name)
Class vs Struct
// FP
func customPrint(person: Person) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: {
print("After 10 seconds: (person.name)")
})
}
struct Person {
var name: String
}
let p = Person(name: "Rodrigo")
customPrint(person: p)
p.name = "Thomas"
print(p.name)
Map
// OO
var values = [Double]()
for step in steps {
values.append(sin(Double(step) / 10))
}
print(values)
// FP
let values = steps.map({ sin(Double($0) / 10) })
print(values)
let steps = (1...100)
Filter
// OO
var partyFriends = [String]()
for friend in friends {
if friend.age >= 18 && friend.age <= 40 {
partyFriends.append(friend.name)
}
}
print(partyFriends)
// FP
let partyFriends = friends
.filter({ $0.age >= 18 && $0.age <= 40 })
.map({ $0.name })
print(partyFriends)
let friends = [
(name: "Pedro", age: 12),
(name: "Maria", age: 18),
(name: "João", age: 21),
(name: "Regina", age: 55)
]
Reduce
// OO
var totalMoney = 0
for money in moneyPile {
totalMoney += money
}
var text = ""
for character in characters {
text += character
}
print(totalMoney)
print(text)
// FP
let totalMoney = moneyPile.reduce(0, +)
let text = characters.reduce("", +)
print(totalMoney)
print(text)
let moneyPile = [10, 20, 15, 30]
let characters = ["a", "b", "c", "d"]
Count Words
// Procedural
let content = readFile()
let words = content.split(separator: " ")
var tally = [String: Int]()
for word in words {
let lowerCaseWord = word.lowercased()
tally[lowerCaseWord] = (tally[lowerCaseWord] ?? 0) + 1
}
var tallyAsArray = [(word: String, count: Int)]()
for (key, value) in tally {
tallyAsArray.append((word: key, count: value))
}
tallyAsArray.sort(by: { $0.count > $1.count })
var top10 = [(word: String, count: Int)]()
for i in (0..<10) {
top10.append(tallyAsArray[i])
}
for value in top10 {
print("(value.word) - (value.count)")
}
Count Words
// OO
class WordCounter {
func printTop10() {
readFileToContent()
splitContentIntoWords()
createTally()
sortTally()
getTop10Words()
printTop10()
}
private var content = ""
private var words = [String.SubSequence]()
private var tally = [String: Int]()
private var tallyAsArray = [(word: String, count: Int)]()
private var top10 = [(word: String, count: Int)]()
private func readFileToContent() { … }
private func splitContentIntoWords() { … }
private func createTally() { … }
private func sortTally() { … }
private func getTop10Words() { … }
private func printTop10Words() { … }
}
let w = WordCounter()
w.printTop10()
Count Words
// FP
let output = readFile()
.split(separator: " ")
.map({ $0.lowercased() })
.reduce([String: Int](), { (tally, lowerCaseWord) in
return tally.merging(
[lowerCaseWord: (tally[lowerCaseWord] ?? 0) + 1],
uniquingKeysWith: { $1 }
)
})
.sorted(by: { $0.value > $1.value })
.prefix(10)
.map({ "($0.key) - ($0.value)" })
.joined(separator: "n")
print(output)
Requests (OO)
let party = CreateParty(delegate: CreatePartyDelegate())
party.create()
class CreateParty: SendAPIRequestDelegate {
init(delegate: CreatePartyDelegate) {
self.delegate = delegate
self.friendsRequest = SendAPIRequest(url: "https://api.com/friends", delegate: self)
self.placeRequest = SendAPIRequest(url: "https://api.com/place", delegate: self)
}
func create() {
self.friendsRequest.request()
self.placeRequest.request()
}
...
}
Requests (OO)
class CreateParty: SendAPIRequestDelegate {
...
// MARK: - SendAPIRequestDelegate
func success(request: SendAPIRequest, json: JSON) {
if request === self.friendsRequest {
self.friends = json.array!.map(toString)
friendsReady = true
tryToCreatePartyData()
}
if request === self.placeRequest {
self.place = json.string!
placeReady = true
tryToCreatePartyData()
}
}
func failure(request: SendAPIRequest, error: APIRequestError) {
if request === self.friendsRequest {
friendsReady = true
tryToCreatePartyData()
}
if request === self.placeRequest {
placeReady = true
tryToCreatePartyData()
}
}
...
}
Requests (OO)
class CreateParty: SendAPIRequestDelegate {
...
// MARK: - Private
private var friendsRequest: SendAPIRequest!
private var placeRequest: SendAPIRequest!
private var friendsReady = false
private var placeReady = false
private var friends: [String]?
private var place: String?
private let delegate: CreatePartyDelegate
private var party: Party?
private func tryToCreatePartyData() {
if !self.friendsReady || !self.placeReady {
return
}
if let friends = self.friends, let place = self.place {
self.party = Party(friends: friends, place: place)
} else {
self.party = nil
}
callDelegateWithPartyData()
}
...
}
Requests (OO)
class CreatePartyDelegate {
func printNoParty() {
print("There will be no party.")
}
func printParty(party: Party) {
print("Go party!!!")
print("Place: (party.place)")
print("Friends:")
for friend in party.friends {
print(friend)
}
}
}
class CreateParty: SendAPIRequestDelegate {
...
// MARK: - Private
...
private func callDelegateWithPartyData() {
guard let party = self.party else {
delegate.printNoParty()
return
}
delegate.printParty(party: party)
}
}
Requests (FP1)
let friendsObservable = sendAPIRequest(url: "https://api.com/friends")
.map(getSuccess >>> map(toArray >>> map(toString)))
let placeObservable = sendAPIRequest(url: "https://api.com/place")
.map(getSuccess >>> map(toString))
let partyObservable = Observable
.zip(friendsObservable, placeObservable)
.map(mergeOptionals >>> map(applyTuple(Party.init)))
let outputObservable = partyObservable.map(
map({
"Go party!!!n"
+ "Place: ($0.place)n"
+ "Friends:n"
+ $0.friends.joined(separator: "n")
})
>>> defaultTo("There will be no party.")
)
_ = outputObservable.subscribe(onNext: { print($0) })
Requests (FP2)
(
"https://api.com/friends",
"https://api.com/place"
)
>>> both(sendAPIRequest >>> map(getSuccess))
>>> first(map(map(toArray >>> map(toString))))
>>> second(map(map(toString)))
>>> applyTuple(Observable.zip)
>>> map(mergeOptionals >>> map(applyTuple(Party.init)))
>>> map(
map({
"Go party!!!n"
+ "Place: ($0.place)n"
+ "Friends:n"
+ $0.friends.joined(separator: "n")
})
>>> defaultTo("There will be no party.")
)
>>> subscribe(onNext: { print($0) })
Requests (FP2’)
let mapFriends: (Observable<JSON?>) -> Observable<[String]?> = map(map(toArray >>> map(toString)))
let mapPlace: (Observable<JSON?>) -> Observable<String?> = map(map(toString))
let requests = both(sendAPIRequest >>> map(getSuccess))
>>> first(mapFriends)
>>> second(mapPlace)
let createParty: ((Observable<[String]?>, Observable<String?>)) -> Observable<Party?> =
applyTuple(Observable.zip)
>>> map(mergeOptionals >>> map(applyTuple(Party.init)))
let toOutputString: (Observable<Party?>) -> Observable<String> = map(
map({
"Go party!!!n"
+ "Place: ($0.place)n"
+ "Friends:n"
+ $0.friends.joined(separator: "n")
})
>>> defaultTo("There will be no party.")
)
let requestStringsToOutput = requests
>>> createParty
>>> toOutputString
_ = requestStringsToOutput((
"https://api.com/friends",
"https://api.com/place"
)).subscribe(onNext: { print($0) })
AI
Conceitos Utilizados
• Dados x Comportamento
• Imperativo vs Declarativo
• Funções puras
• Imutabilidade
• Funções de alta ordem
• Functors, Applicatives e Monads
• Maybe (Optional)
• Array
• Result
• Promise / Observable
Haskell vs. Ada vs. C++ vs. Awk vs. ...
An Experiment in Software Prototyping
Productivity∗
Paul Hudak Mark P. Jones
Yale University
Department of Computer Science
New Haven, CT 06518 {hudak-paul,jones-mark}@cs.yale.edu
July 4, 1994
https://www.linkedin.com/in/rodrigoaruiz/

TDC2018SP | Trilha Prog Funcional - Programacao funcional na pratica com Swift

  • 1.
    Programação Funcional na Práticacom Swift Rodrigo Amorim Ruiz Vikki (vikki.ai)
  • 2.
    Porque Swift • Passagempor valor • Tipagem estática + inferência • Optional • Lambda e outras construções de FP • Enum
  • 3.
    VS Programação Orientada aObjetos Programação Funcional Comparação
  • 4.
    Considerações • Swift vsCocoa • Questão de estilo • OO / Procedural / Imperativo • Técnicas de FP em OO • Performance • Leitura vs Escrita • Melhor ferramenta para cada problema
  • 5.
  • 6.
  • 8.
  • 10.
    Class vs Struct //OO func customPrint(person: Person) { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: { print("After 10 seconds: (person.name)") }) } class Person { var name: String init(name: String) { self.name = name } } let p = Person(name: "Rodrigo") customPrint(person: p) p.name = "Thomas" print(p.name)
  • 11.
    Class vs Struct //FP func customPrint(person: Person) { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: { print("After 10 seconds: (person.name)") }) } struct Person { var name: String } let p = Person(name: "Rodrigo") customPrint(person: p) p.name = "Thomas" print(p.name)
  • 12.
    Map // OO var values= [Double]() for step in steps { values.append(sin(Double(step) / 10)) } print(values) // FP let values = steps.map({ sin(Double($0) / 10) }) print(values) let steps = (1...100)
  • 13.
    Filter // OO var partyFriends= [String]() for friend in friends { if friend.age >= 18 && friend.age <= 40 { partyFriends.append(friend.name) } } print(partyFriends) // FP let partyFriends = friends .filter({ $0.age >= 18 && $0.age <= 40 }) .map({ $0.name }) print(partyFriends) let friends = [ (name: "Pedro", age: 12), (name: "Maria", age: 18), (name: "João", age: 21), (name: "Regina", age: 55) ]
  • 14.
    Reduce // OO var totalMoney= 0 for money in moneyPile { totalMoney += money } var text = "" for character in characters { text += character } print(totalMoney) print(text) // FP let totalMoney = moneyPile.reduce(0, +) let text = characters.reduce("", +) print(totalMoney) print(text) let moneyPile = [10, 20, 15, 30] let characters = ["a", "b", "c", "d"]
  • 15.
    Count Words // Procedural letcontent = readFile() let words = content.split(separator: " ") var tally = [String: Int]() for word in words { let lowerCaseWord = word.lowercased() tally[lowerCaseWord] = (tally[lowerCaseWord] ?? 0) + 1 } var tallyAsArray = [(word: String, count: Int)]() for (key, value) in tally { tallyAsArray.append((word: key, count: value)) } tallyAsArray.sort(by: { $0.count > $1.count }) var top10 = [(word: String, count: Int)]() for i in (0..<10) { top10.append(tallyAsArray[i]) } for value in top10 { print("(value.word) - (value.count)") }
  • 16.
    Count Words // OO classWordCounter { func printTop10() { readFileToContent() splitContentIntoWords() createTally() sortTally() getTop10Words() printTop10() } private var content = "" private var words = [String.SubSequence]() private var tally = [String: Int]() private var tallyAsArray = [(word: String, count: Int)]() private var top10 = [(word: String, count: Int)]() private func readFileToContent() { … } private func splitContentIntoWords() { … } private func createTally() { … } private func sortTally() { … } private func getTop10Words() { … } private func printTop10Words() { … } } let w = WordCounter() w.printTop10()
  • 17.
    Count Words // FP letoutput = readFile() .split(separator: " ") .map({ $0.lowercased() }) .reduce([String: Int](), { (tally, lowerCaseWord) in return tally.merging( [lowerCaseWord: (tally[lowerCaseWord] ?? 0) + 1], uniquingKeysWith: { $1 } ) }) .sorted(by: { $0.value > $1.value }) .prefix(10) .map({ "($0.key) - ($0.value)" }) .joined(separator: "n") print(output)
  • 18.
    Requests (OO) let party= CreateParty(delegate: CreatePartyDelegate()) party.create() class CreateParty: SendAPIRequestDelegate { init(delegate: CreatePartyDelegate) { self.delegate = delegate self.friendsRequest = SendAPIRequest(url: "https://api.com/friends", delegate: self) self.placeRequest = SendAPIRequest(url: "https://api.com/place", delegate: self) } func create() { self.friendsRequest.request() self.placeRequest.request() } ... }
  • 19.
    Requests (OO) class CreateParty:SendAPIRequestDelegate { ... // MARK: - SendAPIRequestDelegate func success(request: SendAPIRequest, json: JSON) { if request === self.friendsRequest { self.friends = json.array!.map(toString) friendsReady = true tryToCreatePartyData() } if request === self.placeRequest { self.place = json.string! placeReady = true tryToCreatePartyData() } } func failure(request: SendAPIRequest, error: APIRequestError) { if request === self.friendsRequest { friendsReady = true tryToCreatePartyData() } if request === self.placeRequest { placeReady = true tryToCreatePartyData() } } ... }
  • 20.
    Requests (OO) class CreateParty:SendAPIRequestDelegate { ... // MARK: - Private private var friendsRequest: SendAPIRequest! private var placeRequest: SendAPIRequest! private var friendsReady = false private var placeReady = false private var friends: [String]? private var place: String? private let delegate: CreatePartyDelegate private var party: Party? private func tryToCreatePartyData() { if !self.friendsReady || !self.placeReady { return } if let friends = self.friends, let place = self.place { self.party = Party(friends: friends, place: place) } else { self.party = nil } callDelegateWithPartyData() } ... }
  • 21.
    Requests (OO) class CreatePartyDelegate{ func printNoParty() { print("There will be no party.") } func printParty(party: Party) { print("Go party!!!") print("Place: (party.place)") print("Friends:") for friend in party.friends { print(friend) } } } class CreateParty: SendAPIRequestDelegate { ... // MARK: - Private ... private func callDelegateWithPartyData() { guard let party = self.party else { delegate.printNoParty() return } delegate.printParty(party: party) } }
  • 22.
    Requests (FP1) let friendsObservable= sendAPIRequest(url: "https://api.com/friends") .map(getSuccess >>> map(toArray >>> map(toString))) let placeObservable = sendAPIRequest(url: "https://api.com/place") .map(getSuccess >>> map(toString)) let partyObservable = Observable .zip(friendsObservable, placeObservable) .map(mergeOptionals >>> map(applyTuple(Party.init))) let outputObservable = partyObservable.map( map({ "Go party!!!n" + "Place: ($0.place)n" + "Friends:n" + $0.friends.joined(separator: "n") }) >>> defaultTo("There will be no party.") ) _ = outputObservable.subscribe(onNext: { print($0) })
  • 23.
    Requests (FP2) ( "https://api.com/friends", "https://api.com/place" ) >>> both(sendAPIRequest>>> map(getSuccess)) >>> first(map(map(toArray >>> map(toString)))) >>> second(map(map(toString))) >>> applyTuple(Observable.zip) >>> map(mergeOptionals >>> map(applyTuple(Party.init))) >>> map( map({ "Go party!!!n" + "Place: ($0.place)n" + "Friends:n" + $0.friends.joined(separator: "n") }) >>> defaultTo("There will be no party.") ) >>> subscribe(onNext: { print($0) })
  • 24.
    Requests (FP2’) let mapFriends:(Observable<JSON?>) -> Observable<[String]?> = map(map(toArray >>> map(toString))) let mapPlace: (Observable<JSON?>) -> Observable<String?> = map(map(toString)) let requests = both(sendAPIRequest >>> map(getSuccess)) >>> first(mapFriends) >>> second(mapPlace) let createParty: ((Observable<[String]?>, Observable<String?>)) -> Observable<Party?> = applyTuple(Observable.zip) >>> map(mergeOptionals >>> map(applyTuple(Party.init))) let toOutputString: (Observable<Party?>) -> Observable<String> = map( map({ "Go party!!!n" + "Place: ($0.place)n" + "Friends:n" + $0.friends.joined(separator: "n") }) >>> defaultTo("There will be no party.") ) let requestStringsToOutput = requests >>> createParty >>> toOutputString _ = requestStringsToOutput(( "https://api.com/friends", "https://api.com/place" )).subscribe(onNext: { print($0) })
  • 25.
  • 26.
    Conceitos Utilizados • Dadosx Comportamento • Imperativo vs Declarativo • Funções puras • Imutabilidade • Funções de alta ordem • Functors, Applicatives e Monads • Maybe (Optional) • Array • Result • Promise / Observable
  • 27.
    Haskell vs. Adavs. C++ vs. Awk vs. ... An Experiment in Software Prototyping Productivity∗ Paul Hudak Mark P. Jones Yale University Department of Computer Science New Haven, CT 06518 {hudak-paul,jones-mark}@cs.yale.edu July 4, 1994
  • 28.