Skip to content

Instantly share code, notes, and snippets.

@evaporei
Last active January 22, 2018 17:49
Show Gist options
  • Save evaporei/2b5debe379fd76a56a917e02ecb8d83c to your computer and use it in GitHub Desktop.
Save evaporei/2b5debe379fd76a56a917e02ecb8d83c to your computer and use it in GitHub Desktop.

Capítulo 3: Currying

O conceito é simples: você pode chamar uma função com menos argumentos do que espera, então ela retorna uma nova função que recebe os argumentos que faltam.

Você pode escolher se vai passar todos os argumentos de uma vez, ou um por vez.

var add = function(x) {
  return function(y) {
    return x + y
  }
}
// ou
const add = x => y => x + y

const increment = add(1)
const addTen = add(10)

increment(2)
// 3

addTen(2)
// 12

Aqui fizemos uma função add que recebe um argumento e retorna uma função. Ao chamá-la, a função retornada lembra do primeiro argumento por closure. Chamando ela com ambos os argumentos não funciona, porém podemos utilizar uma função para facilitar isso, chamada curry.

const { curry } = require('ramda')

const match = curry(
  (what, str) => str.match(what)
)

const replace = curry(
  (what, replacement, str) => str.replace(what, replacement)
)

const filter = curry(
  (fn, array) => array.filter(fn)
)

const map = curry(
  (fn, array) => array.map(fn)
)

Se você observar, o padrão usado nas funções acima é de receber os dados os quais estamos realizando uma operação (String, Array) no último argumento. Isso tem um motivo e vai ficar mais claro o porque desse formato.

match(/\s+/g, 'hello world')
// [ ' ' ]

match(/\s+/g)('hello world')
// [ ' ' ]

const hasSpaces = match(/\s+/g)
// function(x) { return x.match(/\s+/g) }
// ou
// x => x.match(/\s+/g)

hasSpaces('hello world')
// [ ' ' ]

hasSpaces('spaceless')
// null

filter(hasSpaces, ['tori_spelling', 'tori amos'])
// ['tori amos']

const findSpaces = filter(hasSpaces)
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
// ou
// xs => xs.filter(x => x.match(/\s+/g))

findSpaces(['tori_spelling', 'tori amos'])
// ['tori amos']

const noVowels = replace(/[aeiouy]/ig)
// function(replacement, x) { return x.replace(/[aeiouy]/ig, replacement) }
// ou
// (replacement, x) => x.replace(/[aeiouy]/ig, replacement)

const censored = noVowels("*")
// function(x) { return x.replace(/[aeiouy]/ig, '*') }
// ou
// x => x.replace(/[aeiouy]/ig, '*')

censored('Chocolate Rain')
// 'Ch*c*l*t* R**n'

O que está sendo demonstrado aqui é a habilidade de "carregar" uma função com um argumento ou dois para receber uma nova função que lembra desses argumentos.

Currying é util para várias coisas. Nós conseguimos fazer novas funções apenas dando a nossas funções base alguns argumentos, como é possível ser visto em hasSpaces, findSpaces e censored.

Nós também temos a habilidade de transformar quaisquer funções que funcionam em únicos elementos, em uma função que pode ser aplicada em arrays simplesmente a envolvendo com um map:

const { map } = require('ramda')

const getProp = propName => obj => obj[propName]

const getAllIds = map(getProp('id'))

const people = [
  { id: 1, name: 'maria' },
  { id: 2, name: 'john' },
  { id: 3, name: 'davis' },
]

getAllIds(people)
// [ 1, 2, 3 ]

const monsters = [
  { id: 23, name: 'godzilla' },
  { id: 42, name: 'king kong' },
]

getAllIds(monsters)
// [ 23, 42 ]

Passando uma função com menos argumentos do que espera é tipicamente chamado de partial application. Aplicando parcialmente uma função pode remover muito código desnecessário. Considere que a função map não fosse curried, seria necessário passar a função que é aplicada em cada elemento do array toda vez.

const peopleIds = map(person => getProp('id', person), people)

const monstersIds = map(monster => getProp('id', monster), monsters)

Nós tipicamente não definimos funções que envolvem arrays, porque podemos simplesmente criar uma que funciona em um item e aplicar um map(nossaFuncao). O mesmo ocorre com sort, filter e outras funções de alta ordem.

Quando falamos sobre funções puras, dissemos que elas recebem 1 entrada e produzem 1 saída. Currying não faz exatamente isso: cada argumento retorna uma nova função esperando o resto dos argumentos. Isso sim é 1 entrada para 1 saída.

Não importa se a saída é uma outra função - ela é qualificada como pura. Nós permitimos mais de um argumento por vez, mas isso é visto como meramente conveniente ao deixar de ter de chamar uma função com um () para cada parametro.

Resumo

Currying é muito útil e é uma ferramenta que faz programação funcional ser menos verbosa.

Nós podemos fazer novas funções úteis on the fly apenas passando menos argumentos e como bônus, teremos retido a definição da função independente do número de argumentos.

Próximo capítulo será sobre outra ferramenta essencial chamada compose.

Exercícios

Pequenas notas antes de começar.

Vamos usar Ramda, pois suas funções já são curried por padrão. E também porque possui a função curry que irá facilitar na criação de novas funções curryable.

Existem alguns testes unitários para rodar contra suas implementações dos exercícios (https://github.com/DrBoolean/mostly-adequate-guide/tree/master/code/part1_exercises)

As respostas estão no repo do livro (https://github.com/DrBoolean/mostly-adequate-guide/tree/master/code/part1_exercises/answers)

const { split, map, filter, match, reduce } = require('ramda')


// Exercício 1
//==============
// Refatore

var words = function(str) {
  return split(' ', str)
}

// Exercício 1a
//==============
// Crie uma função que funcione em um array de strings.

var sentences = undefined


// Exercício 2
//==============
// Refatore

var filterQs = function(xs) {
  return filter(function(x) {
    return match(/q/i, x)
  }, xs)
}


// Exercício 3
//==============
// Usar função keepHighest para refatorar max

// DEIXE ASSIM / NÃO MEXER:
var keepHighest = function(x, y) {
  return x >= y ? x : y
}

// REFATORE ESSA
var max = function(xs) {
  return reduce(function(acc, x) {
    return keepHighest(acc, x)
  }, -Infinity, xs)
}

// Bônus 1:
// ============
// Implementar `slice` para que seja funcional e curried
// //[1, 2, 3].slice(0, 2)
var slice = undefined


// Bônus 2:
// ============
// Use slice para definir uma função "take" que retorna n elementos do início do array. Tem de ser curried.
// Para ['a', 'b', 'c'] com n=2 deveria retornar ['a', 'b'].
var take = undefined
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment