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