Programação Funcional

Uma introdução

# whoami

root Victor Perin

  • Organizador do NodeSchool Campinas
  • Desenvolvedor NodeJS na InGaia
  • Desculpem pelos vicios de linguagem
  • ❤️ Cryptography | Cypherpunks | Bitcoins
  • ❤️ JS | Docker | NoSQL
  • ❤️ FP | Reactive | Async
  • ❤️ Automata | Computer Science | etc

Gimme bitcoins!1!

Por que aprender?

Introdução

Em ciência da computação, programação funcional é um paradigma de programação que trata a computação como uma avaliação de funções matemáticas e que evita estados ou dados mutáveis. Ela enfatiza a aplicação de funções, em contraste da programação imperativa, que enfatiza mudanças no estado do programa.

 

Pédia, Wiki.

Primeiros Exemplos

function doubleItems (arr) {
  let results = []
  for (let i = 0; i < arr.length; i++){
    results.push(arr[i] * 2)
  }
  return results
}
const doubleItems = (items) =>
  items.map(
    (item) => item * 2
  )
function sumItems (arr) {
  let result = 0
  for (let i = 0; i < arr.length; i++){
    result += arr[i]
  }
  return result
}
const sumItems = items =>
  items.reduce(
    (previous, current) => previous + current
  )

Functional

Imperative

Vantagens da FP

vs OOutros paradígmas

  • Mais declarativo
  • Isolamento de efeitos colaterais
  • Facilidade de testar
  • Simplicidade matemática
  • Facilidade de paralelizar
  • Imutabilidade

Mitos da FP

  • Ah, eu ouvi que FP gasta mais memória
  • Ah, eu vejo que FP é mais complexa
  • Ah, mas não tem "Design Patterns"
  • Ah, mas o pessoal do trampo falou que não escala

Depende muito do Dev

Princípios

  • Funções de primeira-classe
  • Funções de primeira-ordem
    • Map | Filter | Reduce
  • Funções puras
  • Imutabilidade
  • Recursividade
    • Otimização de chamada final
  • Currying
  • Composição de funções
  • Pattern Matching

First-class functions

Funções são tratadas como first-class citizens

(são tratadas como um tipo de variável, como strings)

const sum = (a, b) => a + b

const makeSum = (a) => (b) => a + b;

High-order functions

Funções podem ser usadas como argumentos de outras funções (ex: callbacks)

const doubleMapper = (number) => number * 2

[1, 2, 3, 4].map( doubleMapper )
// [2, 4, 6, 8]



setInterval( () => console.log('data'), 3000 )

Map

Invoca a função callback passada por argumento para cada elemento do Array e devolve um novo Array como resultado.

const doubleItems = [1, 2, 3, 4].map( (number) => number * 2 )

doubleItems // [ 2, 4, 6, 8 ]

1

2

3

4

5

6

2

4

6

8

10

12

Filter

Cria um novo array com todos os elementos que passaram no teste implementado pela função fornecida.

const isOdd = (value) => value % 2

const filtered = [1, 2, 3, 4, 5, 6].filter(isOdd)
// [ 1, 3, 5 ]

1

2

3

4

5

6

1

 

3

 

5

 

Reduce

Utiliza uma função sobre um acumulador e cada elemento do array, reduzindo-a a um único valor.

const sumReducer = (previous, actual) =>
  previous + actual

[0, 1, 2, 3, 4].reduce(sumReducer)
// 10

Pure functions

São funções que não tem efeito colateral.
Um mesmo input sempre gera o mesmo output.

const pureFunction = (a + b) => a + b;

const b;
const impureFunction = (a) => a + b;

Imutability

É o conceito de não alterar informações.
A idéia é pensar em nunca alterar variáveis.

// puro e imutável
const counter = 0
const increments = (value = 0) => value + 1

increments(counter) // 1
increments(counter) // 1
increments(counter) // 1

// vs

// puro e mutável
let counter = 0
const increments = (value = 0) => value + 1

counter = increments(counter) // 1
counter = increments(counter) // 2
counter = increments(counter) // 3

PS: JavaScript não força esse conceito.

Vira responsabilidade do Dev.

Impuro / Mutável

const sales = [
  { client_id: 1 },
  { client_id: 2 },
  { client_id: 3 },
  { client_id: 4 },
]
const getClient = (id) => id + 2

const addClientsInSales = sales => {
  sales.forEach( sale => {
    sale.client = getClient(sale.client_id)
  })
}

addClientsInSales(sales) // undefined

sales
// [
//   { client_id: 1, client: 3 },
//   { client_id: 2, client: 4 },
//   { client_id: 3, client: 5 },
//   { client_id: 4, client: 6 }
// ]

Exemplo

Resultado

Puro / Imutável

const sales = [
  { client_id: 1 },
  { client_id: 2 },
  { client_id: 3 },
  { client_id: 4 },
]
const getClient = (id) => id + 2

const addClientsInSales = sales => {
  return sales.map( sale => {
    return {
      client: getClient(sale.client_id),
      ...sale
    }
    
  })
}
addClientsInSales(sales)
// [
//   { client: 3, client_id: 1 },
//   { client: 4, client_id: 2 },
//   { client: 5, client_id: 3 },
//   { client: 6, client_id: 4 }
// ]


sales
// [
//   { client_id: 1 },
//   { client_id: 2 },
//   { client_id: 3 },
//   { client_id: 4 }
// ]

Exemplo

Resultado

const sales = [
  { client_id: 1 },
  { client_id: 2 },
  { client_id: 3 },
  { client_id: 4 },
]
const getClient = (id) => id + 2

const addClientsInSales = sales =>
  sales.map( sale =>
    ({
      client: getClient(sale.client_id),
      ...sale
    })
  )

Recursion

Funções que chamam elas mesmas para processar alguma informação.

const findUsers = (ids, retries = 5) =>
    database
        .findUsers(ids)
        .catch( (error) => {
          console.error(error)
          
          if(retries)
            return findUsers(ids, retries - 1 )
        })

Tail Call Optimization

Em vez de adicionar à call stack,

você muda o ultimo item da mesma.

const awaysExecute = (func) =>
  func()
    .then( () => awaysExecute(func) )

Currying

É a transformação de uma função que recebe múltiplos parâmetros de forma que ela pode ser chamada como uma cadeia de funções que recebem somente um (ou mais) parâmetro cada.

const normalFunction = (a,b,c) => a + b + c

const { curry, curryRight } = require('lodash')
const curriedFunction = curry(normalFunction)


curriedFunction(a)(b)(c)

curriedFunction(a, b)(c)

curriedFunction(a, b, c)

curriedFunction(a)(b, c)



const curriedRightFunction = curryRight(normalFunction)

curriedRightFunction(c)(b)(a)

TL:DR

uma função que pode retornar uma função para receber mais parametros.

Function Composition

É o conceito de compor 2 funções em uma única.

X ---> Y       Y ---> Z

X ---> Z

const functionsToExecute = [ func1, func2, func3 ]


const { compose, reverse } = require('ramda')
const composedFunction = compose( reverse(functionToExecute) )
// function


const composedFunction = (data) =>
  Promise.resolve(data)
    .then(func1)
    .then(func2)
    .then(func3)

Pattern Matching

É o conceito de executar uma função apenas quando os dados de input batem com o padrão.

// https://github.com/tc39/proposal-pattern-matching

const res = await fetch(jsonService)

const val = match (res) {
  {status: 200, headers: {'Content-Length': s}} => `size is ${s}`,
  {status: 404} => 'JSON not found',
  {status} if (status >= 400) => throw new RequestError(res),
  default => throw default;
}

Relembrando

  • Funções de primeira-classe
  • Funções de primeira-ordem
    • Map | Filter | Reduce
  • Funções puras
  • Imutabilidade
  • Recursividade
    • Tail Call Optimization
  • Currying
  • Function Composition
  • Pattern Matching

The End!

Introdução a Programação Funcional

By Victor Perin

Introdução a Programação Funcional

  • 739