Skip to content

Webschool-io/jargoes-programacao-funcional-para-iniciantes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jargões da Programação Funcional para iniciantes

O objetivo desse documento é definir os jargões da programação funcional em português com exemplos bem simples, para que um inicante possa entender e consiga adaptar-se mais facilmente à esse paradigma.

Isso aqui é um WIP Sinta-se a vontade para enviar um PR )

Esse documento usa termos definidos na Fantasy Land spec quando aplicável.

Aridade

Original: O número de argumentos que uma função recebe. Vem das palavras tipo unário, binário, ternário, etc. Essa palavra possui a característica de ser formada por dois sufixos, "-ário" e "-idade". Soma, por exemplo, recebe dois argumentos, portanto é definido como uma função binária ou uma função com aridade dois. Funções assim podem ser chamadas de "Diádica" pelos amantes das raízes gregas ao invés das latinas. Da mesma forma, uma função que recebe um número variável de argumentos é chamada de "variádica", enquanto uma função binária deve possuir dois e apenas dois argumentos, não obstante o currying e a aplicação parcial (veja abaixo).

Para iniciantes: Quantidade de parâmetros que a função recebe. Por exemplo a função de somar, ela recebe 2 números a serem somados, logo sua aridade é 2.

Também pode ser chamada de [bi]nária, daí vem aridade, ou [di]ádica. Uma função que recebe um número variável ou desconhecido de parâmetro chama-se [vari]ádica.

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

const aridade = soma.length
console.log('A aridade da função é:', aridade) // A aridade da função é 2

// Logo aridade de soma é 2

Funções de Ordem Maior (HOF)

Uma função que recebe outra função como argumento e/ou retorna uma função.

const filter = (pred, xs) => {
    const result = []
    for (let idx = 0 idx < xs.length idx++) {
        if (pred(xs[idx])) {
            result.push(xs[idx])
        }
    }
    return result
}
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]

Aplicações Parciais

O processo de receber uma função com menor aridade comparada à função original, corrigindo o número de argumentos, é conhecido como aplicação parcial.

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

// aplicando parcialmente `a` para `40`
let partial = sum.bind(null, 40)

// invocando com b `b`
partial(2) // 42

Currying

O processo de converter uma função que recebe múltiplos argumentos em uma função que os recebe um de cada vez.

Cada vez que a função é chamada, aceita somente um argumento e retorna uma função que recebe um argumento até que todos sejam passados.

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

const curriedSum = (a) => (b) => a + b

curriedSum(40)(2) // 42.

const add2 = curriedSum(2) // (b) => 2 + b

add2(10) // 12

Composição de Função

O ato de colocar duas funções juntas para forma uma terceira função onde a saída dessa função é a entrada de outra.

const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Uso
floorAndToString(121.212121) // "121"

Pureza

Uma função é pura quando o valor de retorno é determinado apenas pelas suas entradas, e não produz efeitos colaterais.

let greet = (name) => "Olá, " + name 

greet("Brianne") // "Olá, Brianne"

Em oposição à:

let greeting

let greet = () => greeting = "Olá, " + window.name

greet() // "Olá, Brianne"

Efeitos Colaterais

Dizemos que uma função ou expressão tem efeitos colaterais se além de retornar um valor, ela interaja com (lê ou escreve) estados mutáveis eternos.

var differentEveryTime = new Date()
console.log("IO é um efeito colateral!")

Idempotente

Uma função é idempotente se ao reaplicá-la ao seu próprio resultado não produz um resultado diferente.

f(f(x)) = f(x)
Math.abs(Math.abs(10))
sort(sort(sort([2,1])))

Estilo Livre de Apontamento

Esse estilo se define ao escrever funções que não identificam explicitamente os argumentos usados. Geralmente requer currying ou outra função de ordem maior. Mais conhecido como programação implícita.

// Dado
let map = (fn) => (list) => list.map(fn)
let add = (a) => (b) => a + b

// Então

// Não é livre de apontamento - `numbers` é um argumento explícito
let incrementAll = (numbers) => map(add(1))(numbers)

// Livre de apontamento - A lista é um argumento implícito
let incrementAll2 = map(add(1))

incrementAll identifica e usa o parâmetro numbers, logo, não é livre de apontamento. incrementAll2 é escrita combinando funções e valores, não fazendo menção aos seus argumentos. Ela é livre de apontamento.

Definição de funções livre de apontamento são como funções normais, mas sem function ou =>.

Predicado

Um predicado é uma função que retorna verdadeiro ou falso de acordo com um valor dado. Um uso comum de predicados é o callback de filtros de array.

const predicate = (a) => a > 2

[1, 2, 3, 4].filter(predicate) // [3, 4]

Contratos

TODO

Funções Guardadas

TODO

Categorias

Objetos com funções associadas que aderem a certas regras. Exemplo: Monóide

Valor

Qualquer coisa que possa ser atribuída a uma variável.

5
Object.freeze({name: 'John', age: 30}) // A função `freeze` força a imutabilidade.
(a) => a
[1]
undefined

Constante

Uma variável que não pode ser reatribuída após ser definida pela primeira vez.

const five = 5
const john = {name: 'John', age: 30}

Constantes são transparentes referencialmente. Ou seja, podem ser substituídas pelos valores que representam sem afetar o resultado.

Com as duas constantes acima, a expressão a seguir vai retornar true sempre.

john.age + five === ({name: 'John', age: 30}).age + (5)

Functor

Um objeto com uma função map que adere a certas regras. Map executa uma função sobre os valores de um objeto e retorna um objeto novo.

Um functor comum no js é Array

[2, 3, 4].map((n) => n * 2) // [4, 6, 8]

Se func é um objeto implementando uma função de map e f e g são funções arbitrárias, então diz-se que func é o functor se a função map adere às seguintes regras.

// identidade
func.map((x) => x) === func

e

// composição
func.map((x) => f(g(x))) === func.map(g).map(f)

Podemos agora ver que Array é um functor pois adere às regras do functor.

[1, 2, 3].map((x) => x) // = [1, 2, 3]

e

let f = (x) => x + 1
let g = (x) => x * 2

[1, 2, 3].map((x) => f(g(x))) // = [3, 5, 7]
[1, 2, 3].map(g).map(f)     // = [3, 5, 7]

Functor Apontado

Um functor com uma função of que coloca qualquer valor individual naquele functor.

Implementação em Array:

Array.prototype.of = (v) => [v]

[].of(1) // [1]

Levantamento

Levantamento é tipo um map, exceto pelo fato que pode ser aplicado a múltiplos functores.

Map é o mesmo que um levantamento sobre uma função de um argumento.

lift((n) => n * 2)([2, 3, 4]) // [4, 6, 8]

Diferente de map, levantamento pode ser usado para combinar valores de múltiplas arrays.

lift((a, b) => a * b)([1, 2], [3]) // [3, 6]

Transparência Referencial

Uma expressão que pode ser substituída pelo seu valor sem alterar o comportamento do programa, é dita como referencialmente transparente.

Vamos supor que temos a função greet:

let greet = () => "Hello World!"

Qualquer invocação de greet pode ser substituida com Hello World!, logo greet é referencialmente transparente.

Raciocínio Equacional

Quando uma aplicação é composta de expressão e desprovida de efeitos colaterais, as verdades sobre o sistema podem ser derivadas de suas partes.

Avaliação Preguiçosa

Avaliação Preguiçosa é um mecanismo de avaliação chamado conforme necessidade que atrasa a avaliação de uma expressão até que seu valor seja requerido. Em linguagens funcionais, isso permite o uso de estruturas tipo listas infinitas, o que não seria possível normalmente em uma linguagem imperativa onde a sequência dos comandos é significante.

let rand = function*() {
    while (1 < 2) {
        yield Math.random()
    }
}
let randIter = rand()
randIter.next() // Cada execução retorna um valor conforme necessário.

Monóide

Monóide é um tipo de dado junto de uma função de dois parâmetros que "combina" dois valores do tipo, onde um valor de identidade que não afeta o resultado da função também existe.

Um monóide bem simples é números e adição.

1 + 1 // 2

O tipo do dado é número e a função é +, a adição dos dois números.

1 + 0 // 1

O valor de identidade é 0, adicionar 0 a qualquer número não vai alterá-lo.

Para algo ser um monóide, também é necessário que o agrupamento das operações não afete o resultado.

1 + (2 + 3) === (1 + 2) + 3 // true

Concatenação de arrays também pode ser chamado de monóide.

[1, 2].concat([3, 4]) // [1, 2, 3, 4]

O valor identidade é um array vazio []

[1, 2].concat([]) // [1, 2]

Se identidade e funções de composições forem dados, as funções foram monóides por elas mesmas.

var identity = (a) => a
var compose = (f, g) => (x) => f(g(x))

compose(foo, identity)  compose(identity, foo)  foo

Monade

Uma monade é um objeto com funções of e chain. chain é tipo map, exceto pelo fato que "desninha" o objeto aninhado resultante.

['cat,dog', 'fish,bird'].chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']

// Em contraste ao map
['cat,dog', 'fish,bird'].map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]

of também é conhecido como return em outras linguagens funcionais. chain também é conhecido como flatmap e bind em outras linguagens.

Comonade

Um objeto que possui as funções extract e extend.

const CoIdentity = (v) => ({
    val: v,
    extract() { return this.val },
    extend(f) { return CoIdentity(f(this)) }
})

Extract tira um valor do functor.

CoIdentity(1).extract() // 1

Extend executa a função na comonade. A função deve retornar o mesmo tipo de comonade.

CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)

Functor Aplicativo

Um functor aplicativo é um objeto com uma função ap. ap aplica uma função no objeto a outro valor em outro objeto do mesmo tipo.

[(a) => a + 1].ap([1]) // [2]

Morfismo

Uma função de transformação.

Isomorfimos

Um par de transformações entre 2 tipos de objetos que é estrutura por natureza e não há perda de dados.

Exemplo, coordenadas 2D devem ser armazenadas em uma array [2,3] ou objeto {x: 2, y: 3}.

// Prover funções para converter em ambas as direções as faz isomórficas.
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})

const coordsToPair = (coords) => [coords.x, coords.y]

coordsToPair(pairToCoords([1, 2])) // [1, 2]

pairToCoords(coordsToPair({x: 1, y: 2})) // {x: 1, y: 2}

Setóide

Um objeto que tem uma função equals que pode ser usada para comparar outros objetos do mesmo tipo.

Vamos transformar Array em setóide:

Array.prototype.equals = (arr) => {
    var len = this.length
    if (len != arr.length) {
        return false
    }
    for (var i = 0 i < len i++) {
        if (this[i] !=== arr[i]) {
            return false
        }
    }
    return true
}

[1, 2].equals([1, 2]) // true
[1, 2].equals([0]) // false

Semigrupo

Um objeto que tem uma função concat que combina com outro objeto deo mesmo tipo.

[1].concat([2]) // [1, 2]

Dobrável

Um objeto que possui uma função reduce que pode transformar o objeto em outro tipo.

let sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6

Traversável

TODO

Assinaturas de Tipo

Frequentemente, as funçẽos vão incluir comentário quy indicam os tipos dos seus argumentos e retornos.

Há uma pequena variância na comunidade, mas geralmente seguem os seguintes padrões:

// nomeDaFunção :: tipoDoPrimeiroArgumento -> tipoDoSegundoArgumento -> tipoDoRetorno

// add :: Number -> Number -> Number
let add = (x) => (y) => x + y

// increment :: Number -> Number
let increment = (x) => x + 1

Se uma função aceita outra função como parâmetro, ela é envolva em parêntese.

// call :: (a -> b) -> a -> b
let call = (f) => (x) => f(x)

As letras a, b, c, d são usadas para mostrar que o argumento pode ser de qualquer tipo. Nesse map, recebe uma função que transforma um valor do tipo a em outro do tipo b, ou um array de valores do tipo a e retorna um array de valores do tipo b.

// map :: (a -> b) -> [a] -> [b]
let map = (f) => (list) => list.map(f)

Tipo de união

Um tipo de união é a combinação de dois tipos em um terceiro.

JS não tem tipos estáticos, mas vamos supor que queremos inventar um tipo NumOrString, que é a soma de um String e um Number.

O operador + no JS funciona em strings e números, então podemos usar esse novo tipo para descrever suas entradas e saídas.

// add :: (NumOrString, NumOrString) -> NumOrString
const add = (a, b) => a + b

add(1, 2) // Retorna número 3
add('Foo', 2) // Retorna string "Foo2"
add('Foo', 'Bar') // Retorna string "FooBar"

Tipos de união também são conhecidos como tipos algebráicos, uniões marcadas, ou tipos de soma.

algumas bibliotecas em JS que ajudam a definir e usar tipos de união.

Tipo de produto

Um tipo de produto combina tipos de uma forma que você provavelmente já está familiarizado.

// point :: (Number, Number) -> {x: Number, y: Number}
const point = (x, y) => ({x: x, y: y})

Isso é chamado de produto porque o total de valores possíveis da estrutura de dados ´e o produto dos valores diferentes.

Veja também Teoria dos conjuntos.

Opção

Opção é um tipo de união com duas classes que geralmente são chamadas de Some (alguma) e None (nenhuma).

Opção é útil para compor funções que podem não retornar um valor.

// Definição inocente

const Some = (v) => ({
    val: v,
    map(f) {
        return Some(f(this.val))
    },
    chain(f) {
        return f(this.val)
    }
})

const None = () => ({
    map(f){
        return this
    },
    chain(f){
        return this
    }
})

// maybeProp :: (String, {a}) -> Option a
const maybeProp = (key, obj) => typeof obj[key] === 'undefined' ? None() : Some(obj[key])

Use chain para colocar em sequência funções que retornam Options

// getItem :: Cart -> Option CartItem
const getItem = (cart) => maybeProp('item', cart)

// getPrice :: Item -> Option Number
const getPrice = (item) => maybeProp('price', item)

// getNestedPrice :: cart -> Option a
const getNestedPrice = (cart) => getItem(obj).chain(getPrice)

getNestedPrice({}) // None()
getNestedPrice({item: {foo: 1}}) // None()
getNestedPrice({item: {price: 9.99}}) // Some(9.99)

Option também é conhecido como Maybe (talvez). Some às vezes é chamada de Just (apenas). None às vezes é chamada de Nothing (nada).


P.S: Sem as contribuções maravilhosas esse repositório não teria sentido algum!

Releases

No releases published

Packages

No packages published