Programação Funcional
Yet another talk sobre FP
"Já sei! Vai explicar o que é Monad"
"Bla bla bla Functors"
"Algebraic Data Types em Haskell"
Esta palestra não é sobre isso (não diretamente :) )
Oi! Hanneli (@hannelita)
- Eng. da Computação
- Programação
- Eletrônica
- Matemática
- Física
- Lego
- Meetups
- Bichinhos
- Café
- GIFs
- Livros
- Pokémon
Disclaimer
Pouco formalismo, muita prática
Exemplo de um problemas simplificados
Não é o melhor código do mundo
Comentários sobre colocar em produção
Alguns GIFs
Sempre há outra maneira de implementar
uma ideia
Mecanismos para aplicar as técnicas de FP
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Entrada: o problema dos livros e engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Entrada: o problema dos livros e engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes o escopo com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
O que seria legal ver nas palestras?
Inserir conceitos de FP em algum projeto em andamento, geralmente em linguagem imperativa
Colocar em produção
Implementar alguma feature isoladamente, sem afetar o sistema principal
Tutoriais no YouTube
Acessado em 26/09/2017
- "Pure Function"
- "Haskell is cool!"
- Exemplo com a classe Shape/Polígonos
- Exemplo teórico, sem implementação (nem esboço)
- HoF, MonadPlus, Category Theory, Lambda Calculus
- map, flatmap, filter, reduce eram os cenários clássicos onde podia ver algum código
Diversas palestras, apesar de muito boas, era pouco práticas
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Entrada:o problema dos livros e engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
Aplicação - Wishlist da Amazon
Aplicação - Wishlist da Amazon
Preciso otimizar minhas aquisições: comprar após observar um valor mínimo, escolher entre ebook e capa comum, procurar por livros usados, etc.
É (praticamente) impossível fazer essa análise manualmente.
- "Pure Function"
- "Haskell is cool!"
- Exemplo com a classe Shape/Polígonos
- Exemplo teórico, sem implementação (nem esboço)
- HoF, MonadPlus, Category Theory, Lambda Calculus
- map, flatmap, filter, reduce eram os cenários clássicos onde podia ver algum código => combinators
Diversas palestras, apesar de muito boas, era pouco práticas
Exercitar os conceitos de map, reduce, listas, filter
Parser para obter os itens da wishlist (JSON local)
Scrapy (em Python)
V1
def parse(self, response):
books_root = response.css("#item-page-wrapper")
books = books_root.xpath('//div[re:test(@id, "itemMain_*")]')
for book in books:
book_info = book.xpath('.//div[re:test(@id, "itemInfo_*")]')
title = self.strformat(book_info.xpath('.//a[re:test(@id,
"itemName_*")]/text()').extract_first())
authors_format = reduce(lambda a, b: a+b,
map(lambda x: x.strip() ,
book_info.xpath('.//div[@class="a-row a-size-small"]/text()').extract()))
price = book_info.xpath('.//span[re:test(@id, "itemPrice_*")]/text()')
.extract_first().strip()
yield {'Title':title, 'Info':authors_format, 'Last price':price }
V1
def parse(self, response):
#...
authors_format = reduce(lambda a, b: a+b,
map(lambda x: x.strip() ,
book_info.xpath('.//div[@class="a-row a-size-small"]/text()')
.extract()))
price = book_info.xpath('.//span[re:test(@id, "itemPrice_*")]/text()')
.extract_first().strip()
#..
Parece funcional (Rááá!), mas há um ponto de falha. Qual?
V1
def parse(self, response):
#...
authors_format = reduce(lambda a, b: a+b,
map(lambda x: x.strip() ,
book_info.xpath('.//div[@class="a-row a-size-small"]/text()')
.extract()))
price = book_info.xpath('.//span[re:test(@id, "itemPrice_*")]/text()')
.extract_first().strip()
#..
Parece funcional (Rááá!), mas há um ponto de falha. Qual?
V1
def parse(self, response):
#...
authors_format = reduce(lambda a, b: a+b,
map(lambda x: x.strip() ,
book_info.xpath('.//div[@class="a-row a-size-small"]/text()')
.extract()))
price = book_info.xpath('.//span[re:test(@id, "itemPrice_*")]/text()')
.extract_first().strip()
#..
.extract_first() pode retornar None() e o .strip() falha.
Idealizando
def parse(self, response):
#...
.extract_first?.strip()
#..
Realidade
def parse(self, response):
#...
price = self.strformat(book_info.xpath
('.//span[re:test(@id, "itemPrice_*")]/text()').extract_first())
#..
def strformat(self, data):
if data != None:
return data.strip()
:(
Ter um Optional | Maybe seria muito útil
Focar o desenvolvimento para Tipos e Operações
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Um cenário para aplicação
- Entrada: engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
O problema das baterias
O problema das baterias
Você possui um conjunto de baterias de chumbo e sabe a a capacidade total (12V, 9V, 24V) de cada uma delas
Você não possui um multímetro para medir as tensões
Você deseja que uma das baterias tenha uma determinada tensão e pode transferir carga de uma para outra
Exemplo
9V
12V
Uma das baterias deve ter um estado 6V. O que devo fazer?
Início - ambas descarregadas (B1: 0V; B2: 0V)
B1
B2
Carregar a Bateria 1 (B1: 9V; B2: 0V)
Transferir a carga da Bateria 1 para a Bateria 2 (B1: 0V; B2: 9V)
Carregar a Bateria 1 (B1: 9V; B2: 9V)
Transferir a carga da Bateria 1 para a Bateria 2 (B1: 6V; B2: 12V)
Exemplo
9V
12V
Uma das baterias deve ter um estado 6V. O que devo fazer?
Início - ambas descarregadas (B1: 0V; B2: 0V)
B1
B2
Carregar a Bateria 1 (B1: 9V; B2: 0V)
Transferir a carga da Bateria 1 para a Bateria 2 (B1: 0V; B2: 9V)
Carregar a Bateria 1 (B1: 9V; B2: 9V)
Transferir a carga da Bateria 1 para a Bateria 2 (B1: 6V; B2: 12V)
Problema clássico de transferir quantidades
Representando o problema e os estados
Conjunto de baterias: Vector[Int]
Carga da bateria: Int
Estados: Vazia, Carga Completa, Transferência de carga
Em cada passo, teremos uma tensão associada a cada bateria. Vamos registrar o histórico de operações
Representando o problema e os estados
Conjunto de baterias: Vector[Int]
Carga da bateria: Int
package batteries
class Charging(voltages: Vector[Int]) {
type State = Vector[Int]
}
Representando o problema e os estados
No início, assumir que estão descarregadas
package batteries
class Charging(voltages: Vector[Int]) {
type State = Vector[Int]
val initialState = voltages map (x => 0)
}
Representando o problema e os estados
Vamos inserir os estados identificados
package batteries
class Charging(voltages: Vector[Int]) {
type State = Vector[Int]
val initialState = voltages map (x => 0)
trait Transfer
case class Empty(battery: Int) extends Transfer
case class FullCharge(battery: Int) extends Transfer
case class Charge(from: Int, to: Int) extends Transfer
}
}
(a trait Transfer age como o Protocol para os três elementos que identificamos)
Representando o problema e os estados
Baterias e transferências possíveis
package batteries
class Charging(voltages: Vector[Int]) {
type State = Vector[Int]
val initialState = voltages map (x => 0)
trait Transfer
case class Empty(battery: Int) extends Transfer
case class FullCharge(battery: Int) extends Transfer
case class Charge(from: Int, to: Int) extends Transfer
val batteries = 0 until voltages.length
val transfers =
( for ( b <- batteries ) yield Empty(b)) ++
(for (b <- batteries) yield FullCharge(b)) ++
(for (from <- batteries; to <- batteries if from != to)
yield Charge(from, to))
}
}
Representando o problema e os estados
Vamos aos caminhos possíveis entre cada estado:
package batteries
class Charging(voltages: Vector[Int]) {
// ...
class Path(history: List[Transfer], val finalVoltage: State){
def extend(charge: Transfer) = new Path(charge :: history,
charge change finalVoltage)
}
val initialPath = new Path(Nil, initialState)
def from(paths: Set[Path], explored: Set[State]):
Stream[Set[Path]] = {
if (paths.isEmpty) Stream.empty
else {
val more = for {
path <- paths
next <- transfers map path.extend
if !(explored contains next.finalVoltage)
} yield next
paths #:: from(more, explored ++ (more map (_.finalVoltage)))
}
}
val pathSets = from(Set(initialPath), Set(initialState))
}
}
Representando o problema e os estados
Vamos aos caminhos possíveis entre cada estado:
package batteries
class Charging(voltages: Vector[Int]) {
// ...
class Path(history: List[Transfer], val finalVoltage: State){
def extend(charge: Transfer) = new Path(charge :: history,
charge change finalVoltage)
}
val initialPath = new Path(Nil, initialState)
def from(paths: Set[Path], explored: Set[State]):
Stream[Set[Path]] = {
if (paths.isEmpty) Stream.empty
else {
val more = for {
path <- paths
next <- transfers map path.extend
if !(explored contains next.finalVoltage)
} yield next
paths #:: from(more, explored ++ (more map (_.finalVoltage)))
}
}
val pathSets = from(Set(initialPath), Set(initialState))
}
}
Representando o problema e os estados
E finalmente, a solução
package batteries
class Charging(voltages: Vector[Int]) {
// ...
def solutions(target: Int): Stream[Path] =
for {
pathSet <- pathSets
path <- pathSet
if path.finalVoltage contains target
} yield path
}
No geral, esses exemplos não são tão realísticos - eles não possuem regras complexas. Como seria um projeto do mercado financeiro?
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Um cenário para aplicação
- Entrada: engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
24 dias de Hackage
http://blog.haskellbr.com/
Exemplo de acesso a API
Source: https://github.com/FranklinChen/twenty-four-days2015-of-hackage/blob/master/src/WreqExample.hs
module WreqExample where
data EventInfo =
EventInfo { eventName :: Text
, venueName :: Text
}
deriving (Show)
type GroupId = Text
meetupEventsUrl :: String
meetupEventsUrl = "https://api.meetup.com/2/events"
Exemplo de acesso a API
Source: https://github.com/FranklinChen/twenty-four-days2015-of-hackage/blob/master/src/WreqExample.hs
module WreqExample where
data EventInfo =
EventInfo { eventName :: Text
, venueName :: Text
}
deriving (Show)
type GroupId = Text
meetupEventsUrl :: String
meetupEventsUrl = "https://api.meetup.com/2/events"
eventsOptions :: GroupId
-> Options
eventsOptions groupId =
set (param "page") ["10"] (
set (param "order") ["time"] (
set (param "status") ["upcoming"] (
set (param "group_id") [groupId] (
set (param "format") ["json"] defaults))))
Um outro jeito
Source: https://github.com/FranklinChen/twenty-four-days2015-of-hackage/blob/master/src/WreqExample.hs
module WreqExample where
import Control.Lens ((&), (.~))
data EventInfo =
EventInfo { eventName :: Text
, venueName :: Text
}
deriving (Show)
type GroupId = Text
meetupEventsUrl :: String
meetupEventsUrl = "https://api.meetup.com/2/events"
eventsOptions :: GroupId
-> Options
eventsOptions groupId = defaults
& param "format" .~ ["json"]
& param "group_id" .~ [groupId]
& param "status" .~ ["upcoming"]
& param "order" .~ ["time"]
& param "page" .~ ["10"]
Lens
Source: https://github.com/FranklinChen/twenty-four-days2015-of-hackage/blob/master/src/WreqExample.hs
module WreqExample where
import Control.Lens ((&), (.~))
data EventInfo =
EventInfo { eventName :: Text
, venueName :: Text
}
deriving (Show)
type GroupId = Text
meetupEventsUrl :: String
meetupEventsUrl = "https://api.meetup.com/2/events"
eventsOptions :: GroupId
-> Options
eventsOptions groupId = defaults
& param "format" .~ ["json"]
& param "group_id" .~ [groupId]
& param "status" .~ ["upcoming"]
& param "order" .~ ["time"]
& param "page" .~ ["10"]
Lens
Família de getters, folders e setters genérico.
class Lens[A,B] {
get: A => B
set: (A, B) => A
}
(Não são aqueles gets e sets de Java!)
Como esse é um meetup de Elm, um exemplo
Ótimo caso de estudo de tipos
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Um cenário para aplicação
- Entrada: engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
Boa notícia
O Heroku suporta quase tudo (Haskell, Scala, Python, Clojure, Elixir)
Má notícia: uma aplicação a mais costuma gerar mais complexidade. Exemplo: troca de JSONs
Agenda
- Primeiros passos em Programação Funcional
- Coisas que deram errado
- Um cenário para aplicação
- Entrada: engatinhando nos conceitos em Python
- Prato Principal: desenvolvendo uma solução completa em Scala
- Sobremesa: Lentes com Haskell
- Colocando em produção - pontos para considerar
- Extras e referências
Dica para quem está começando
- Experimente problemas simples e tente olhar casos em que há (bons) exemplos
- Conhecer uma linguagem em que os tipos garantem seu código ajuda
- Pense em um problema mais complicado e modele os estados, operações e tipos
- Tente aplicar os conceitos teóricos
- Cuidado com exemplos complicados demais: às vezes eles mais confundem do que colaboram
O que não coube nessa palestra
- Mais conceitos aplicados: FRP, Futures
- Uma introdução aos ADTs e por que eles podem ser úteis
- Trazer casos de outras linguagens (Rust, Elixir, Clojure, Ocaml, Idris)
- IO
Referências
- https://www.slideshare.net/debasishg/qconny-12
- https://www.slideshare.net/ScottWlaschin/fp-patterns-buildstufflt
- https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/type-families-and-pokemon
- https://github.com/debasishg/codemesh14
- https://www.eventials.com/Globalcode/tdc-sp-2017-stadium-terca-8/?playlist=tdconline-sao-paulo-2017-stadium
- https://www.eventials.com/Globalcode/tdc-sp-2017-stadium-sabado-6/
Menu-Degustação-FP
By Hanneli Tavante (hannelita)
Menu-Degustação-FP
- 2,103