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
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
Acessado em 26/09/2017
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.
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 }
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?
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?
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.
def parse(self, response):
#...
.extract_first?.strip()
#..
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()
Focar o desenvolvimento para Tipos e Operações
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
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)
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)
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
Conjunto de baterias: Vector[Int]
Carga da bateria: Int
package batteries
class Charging(voltages: Vector[Int]) {
type State = Vector[Int]
}
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)
}
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
}
}
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))
}
}
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))
}
}
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))
}
}
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
}
http://blog.haskellbr.com/
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"
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))))
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"]
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"]
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!)
Ótimo caso de estudo de tipos
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