Se você tentar a programação funcional, significa que em breve encontrará o conceito de funções puras. Ao continuar, você descobrirá que os programadores que preferem um estilo funcional parecem obcecados por esses recursos. Eles dizem que funções puras permitem que você fale sobre código. Eles dizem que funções puras são entidades que dificilmente funcionam tão imprevisivelmente que levarão a uma guerra termonuclear. Você também pode aprender com esses programadores que funções puras fornecem transparência referencial. E assim - ao infinito.
A propósito, programadores funcionais estão certos. Funções puras são boas. Mas há um problema ...
O autor do material, cuja tradução apresentamos à sua atenção, quer falar sobre como lidar com os efeitos colaterais em funções puras.
O problema das funções puras
Uma função pura é uma função que não tem efeitos colaterais (na verdade, essa não é uma definição completa de uma função pura, mas retornaremos a essa definição). No entanto, se você pelo menos entende alguma coisa na programação, sabe que a coisa mais importante aqui são precisamente os efeitos colaterais. Por que calcular o número Pi até a centésima casa decimal se ninguém pode ler esse número? Para exibir algo na tela ou imprimir em uma impressora ou apresentá-lo de alguma outra forma, acessível para percepção, precisamos chamar o comando apropriado do programa. E qual é a utilidade dos bancos de dados se nada puder ser escrito neles? Para garantir a operação dos aplicativos, você precisa ler os dados dos dispositivos de entrada e solicitar informações dos recursos da rede. Tudo isso não pode ser feito sem efeitos colaterais. Mas, apesar desse estado de coisas, a programação funcional é construída em torno de funções puras. Então, como os programadores que escrevem programas em um estilo funcional conseguem resolver esse paradoxo?
Se você responder a essa pergunta em poucas palavras, os programadores funcionais fazem o mesmo que os matemáticos: eles trapaceiam. Embora, apesar dessa acusação, deva-se dizer que eles, do ponto de vista técnico, simplesmente seguem certas regras. Mas eles encontram brechas nessas regras e as expandem para tamanhos incríveis. Eles fazem isso de duas maneiras principais:
- Eles aproveitam a injeção de dependência. Eu chamo isso de jogar um problema por cima de uma cerca.
- Eles usam functores, o que me parece uma forma extrema de procrastinação. Deve-se notar aqui que, em Haskell, é chamado de "IO functor" ou "IO monad"; no PureScript, o termo "Effect" é usado, o que, na minha opinião , é um pouco melhor para descrever a essência dos functores.
Injeção de Dependência
A injeção de dependência é a primeira maneira de lidar com os efeitos colaterais. Usando essa abordagem, pegamos tudo o que polui o código e o colocamos nos parâmetros da função. Então, podemos considerar tudo isso como algo que faz parte da responsabilidade de alguma outra função. Vou explicar isso com o seguinte exemplo:
// logSomething :: String -> String function logSomething(something) { const dt = (new Date())toISOString(); console.log(`${dt}: ${something}`); return something; }
Aqui, gostaria de fazer uma anotação para aqueles que estão familiarizados com as assinaturas de tipo. Se seguíssemos rigorosamente as regras, teríamos que levar em conta aqui os efeitos colaterais. Mas vamos lidar com isso mais tarde.
A função
logSomething()
tem dois problemas que impedem que ela seja declarada limpa: cria um objeto
Date
e gera algo no console. Ou seja, nossa função não apenas realiza operações de entrada e saída, mas também produz, quando é chamada em momentos diferentes, resultados diferentes.
Como limpar esta função? Usando a técnica de injeção de dependência, podemos pegar tudo o que polui a função e torná-la parâmetros de função. Como resultado, em vez de aceitar um parâmetro, nossa função aceitará três parâmetros:
// logSomething: Date -> Console -> String -> * function logSomething(d, cnsl, something) { const dt = d.toIsoString(); return cnsl.log(`${dt}: ${something}`); }
Agora, para chamar a função, precisamos transferir tudo para ela que a poluiu antes:
const something = "Curiouser and curiouser!" const d = new Date(); logSomething(d, console, something);
Aqui você pode pensar que tudo isso não faz sentido, que apenas aumentamos o problema um nível acima, e isso não adicionou pureza ao nosso código. E você sabe, esses são os pensamentos certos. Esta é uma brecha na sua forma mais pura.
Isso é como um analfabetismo falso: “Eu não sabia que chamar o método
log
do objeto
cnsl
levaria à execução da instrução de E / S. Alguém acabou de me entregar, mas eu não sei de onde tudo veio. ” Essa atitude está errada.
E, de fato, o que está acontecendo não é tão estúpido quanto parece à primeira vista.
logSomething()
os recursos da função
logSomething()
. Se você deseja fazer algo impuro, deve fazê-lo você mesmo. Digamos que você possa passar vários parâmetros para esta função:
const d = {toISOString: () => '1865-11-26T16:00:00.000Z'}; const cnsl = { log: () => { // }, }; logSomething(d, cnsl, "Off with their heads!"); // "Off with their heads!"
Agora nossa função não faz nada (apenas retorna o parâmetro
something
). Mas ela é completamente pura. Se você chamá-lo com os mesmos parâmetros várias vezes, ele retornará a mesma coisa todas as vezes. E esse é o ponto. Para tornar essa função impura, precisamos executar intencionalmente determinadas ações. Ou, em outras palavras, tudo o que uma função depende está em sua assinatura. Ele não acessa nenhum objeto global como
console
ou
Date
. Isso formaliza tudo.
Além disso, é importante observar que podemos transferir outras funções para nossa função, que não estavam limpas anteriormente. Veja outro exemplo. Imagine que, de alguma forma, exista um nome de usuário e precisamos obter o valor do campo correspondente deste formulário:
// getUserNameFromDOM :: () -> String function getUserNameFromDOM() { return document.querySelector('#username').value; } const username = getUserNameFromDOM(); username; // "mhatter"
Nesse caso, estamos tentando carregar algumas informações do DOM. As funções puras não fazem isso, pois o
document
é um objeto global que pode ser alterado a qualquer momento. Uma maneira de limpar essa função é passar o objeto de
document
global como parâmetro. No entanto, você ainda pode passar a função
querySelector()
para
querySelector()
. É assim:
// getUserNameFromDOM :: (String -> Element) -> String function getUserNameFromDOM($) { return $('#username').value; } // qs :: String -> Element const qs = document.querySelector.bind(document); const username = getUserNameFromDOM(qs); username; // "mhatter"
Aqui, novamente, você pode pensar que isso é estúpido. Afinal, aqui simplesmente removemos da função
getUsernameFromDOM()
o que não nos permite chamá-la de limpa. No entanto, não nos livramos disso, apenas transferindo a chamada para o DOM para outra função,
qs()
. Pode parecer que o único resultado perceptível dessa etapa foi que o novo código era mais longo que o antigo. Em vez de uma função impura, agora temos duas funções, uma das quais ainda é imunda.
Espere um pouco. Imagine que precisamos escrever um teste para a função
getUserNameFromDOM()
. Agora, comparando as duas opções para essa função, pense em qual delas será mais fácil trabalhar? Para que a versão suja da função funcione, precisamos de um objeto de documento global. Além disso, este documento deve ter um elemento com o identificador de
username
. Se você precisar testar uma função semelhante fora do navegador, precisará usar algo como JSDOM ou um navegador sem uma interface com o usuário. Observe que tudo isso é necessário apenas para testar uma função pequena com um comprimento de várias linhas. E, para testar a segunda versão limpa dessa função, basta fazer o seguinte:
const qsStub = () => ({value: 'mhatter'}); const username = getUserNameFromDOM(qsStub); assert.strictEqual('mhatter', username, `Expected username to be ${username}`);
Obviamente, isso não significa que, para testar essas funções, não sejam necessários testes de integração executados em um navegador real (ou, pelo menos, usando algo como JSDOM). Mas este exemplo demonstra uma coisa muito importante, que é agora que a função
getUserNameFromDOM()
tornou completamente previsível. Se passarmos
qsStub()
para ele, ele sempre retornará
mhatter
. A "imprevisibilidade" que passamos para a pequena função
qs()
.
Se necessário, podemos levar mecanismos imprevisíveis a níveis ainda mais distantes da função principal. Como resultado, podemos movê-los, relativamente falando, para as “áreas de fronteira” do código. Isso resultará em uma fina camada de código impuro que envolve um kernel bem testado e previsível. A previsibilidade do código acaba sendo extremamente valiosa quando o tamanho dos projetos criados pelos programadores aumenta.
▍ Desvantagens do mecanismo de injeção de dependência
Usando a injeção de dependência, você pode gravar um aplicativo grande e complexo. Eu sei disso, já que eu próprio escrevi
esse pedido . Com essa abordagem, o teste é simplificado e as dependências de funções se tornam claramente visíveis. Mas a injeção de dependência não está isenta de falhas. O principal é que, quando usado, assinaturas de funções muito longas podem ser obtidas:
function app(doc, con, ftch, store, config, ga, d, random) { // } app(document, console, fetch, store, config, ga, (new Date()), Math.random);
De fato, isso não é tão ruim. As desvantagens de tais construções se manifestam se alguns dos parâmetros precisarem ser passados para determinadas funções que estão profundamente embutidas em outras funções. Parece que é necessário passar parâmetros por vários níveis de chamadas de função. Quando o número de tais níveis aumenta, começa a incomodar. Por exemplo, pode ser necessário transferir o objeto que representa a data através de 5 funções intermediárias, enquanto nenhuma das funções intermediárias usa esse objeto. Embora, é claro, não se possa dizer que tal situação seja algo como uma catástrofe universal. Além disso, isso possibilita ver claramente as dependências das funções. No entanto, seja como for, isso ainda não é tão agradável. Portanto, consideramos o seguinte mecanismo.
Functions funções preguiçosas
Vamos dar uma olhada na segunda brecha usada pelos adeptos da programação funcional. Consiste na seguinte idéia: um efeito colateral não é um efeito colateral até que realmente ocorra. Eu sei que isso soa misterioso. Para descobrir isso, considere o seguinte exemplo:
// fZero :: () -> Number function fZero() { console.log('Launching nuclear missiles'); // return 0; }
Um exemplo talvez seja estúpido, eu sei disso. Se precisarmos do número 0, para que ele apareça, basta inseri-lo no lugar certo no código. E também sei que você não escreverá código JavaScript para controlar armas nucleares. Mas precisamos desse código para ilustrar a tecnologia em questão.
Então, aqui está um exemplo de uma função impura. Ele envia dados para o console e também é a causa da guerra nuclear. No entanto, imagine que precisamos do zero que essa função retorna. Imagine um cenário em que precisamos calcular algo após o lançamento de um foguete. Digamos que possamos precisar iniciar um temporizador de contagem regressiva ou algo assim. Nesse caso, seria completamente natural pensar antecipadamente em fazer os cálculos. E devemos garantir que o foguete seja lançado exatamente quando necessário. Não precisamos realizar cálculos de forma que eles possam acidentalmente levar ao lançamento deste foguete. Então, vamos pensar no que acontece se envolvermos a função
fZero()
em outra função que simplesmente a retorna. Digamos que será algo como um wrapper de segurança:
// fZero :: () -> Number function fZero() { console.log('Launching nuclear missiles'); // return 0; } // returnZeroFunc :: () -> (() -> Number) function returnZeroFunc() { return fZero; }
Você pode chamar a função
returnZeroFunc()
quantas vezes quiser. Nesse caso, até que a implementação do que ele retorna seja realizada, nós (teoricamente) estamos seguros. No nosso caso, isso significa que a execução do código a seguir não levará a uma guerra nuclear:
const zeroFunc1 = returnZeroFunc(); const zeroFunc2 = returnZeroFunc(); const zeroFunc3 = returnZeroFunc();
Agora um pouco mais rigoroso do que antes, vamos abordar a definição do termo "função pura". Isso nos permitirá examinar a função
returnZeroFunc()
com mais detalhes. Portanto, a função é limpa nas seguintes condições:
- Sem efeitos colaterais observados.
- Transparência do link. Ou seja, chamar uma função com os mesmos valores de entrada sempre leva aos mesmos resultados.
returnZeroFunc()
analisar a função
returnZeroFunc()
.
Ela tem algum efeito colateral? Acabamos de descobrir que chamar
returnZeroFunc()
não lança mísseis. Se você não chamar o que essa função retorna, nada acontecerá. Portanto, podemos concluir que essa função não tem efeitos colaterais.
Esse recurso é referencialmente transparente? Ou seja, ele sempre retorna o mesmo ao passar os mesmos dados de entrada para ele? Vamos verificar isso, aproveitando o fato de que, no fragmento de código acima, chamamos essa função várias vezes:
zeroFunc1 === zeroFunc2; // true zeroFunc2 === zeroFunc3; // true
Tudo parece bom, mas a função
returnZeroFunc()
ainda não está completamente limpa. Ela se refere a uma variável que está fora de seu próprio escopo. Para resolver esse problema, reescrevemos a função:
// returnZeroFunc :: () -> (() -> Number) function returnZeroFunc() { function fZero() { console.log('Launching nuclear missiles'); // return 0; } return fZero; }
Agora a função pode ser considerada limpa. No entanto, nessa situação, as regras de JavaScript jogam contra nós. Ou seja, não podemos mais usar o operador
===
para verificar a transparência referencial de uma função. Isso se deve ao fato de
returnZeroFunc()
sempre retornar uma nova referência para a função. É verdade que a transparência do link pode ser verificada examinando você mesmo o código. Essa análise mostrará que, a cada chamada de função, ele retorna um link para a mesma função.
Diante de nós está uma pequena brecha. Mas pode ser usado em projetos reais? A resposta a esta pergunta é positiva. No entanto, antes de falar sobre como usar isso na prática, desenvolveremos um pouco nossa idéia. Ou seja,
fZero()
retornar à função perigosa
fZero()
:
// fZero :: () -> Number function fZero() { console.log('Launching nuclear missiles'); // return 0; }
Vamos tentar usar o zero retornado por essa função, mas faremos isso para que (até agora) uma guerra nuclear não comece. Para fazer isso, crie uma função que
fZero()
o zero retornado pela função
fZero()
e adicione uma a ela:
// fIncrement :: (() -> Number) -> Number function fIncrement(f) { return f() + 1; } fIncrement(fZero); // // 1
Isso é azar ... Começamos acidentalmente uma guerra nuclear. Vamos tentar novamente, mas desta vez não retornaremos um número. Em vez disso, retornamos uma função que um dia retorna um número:
// fIncrement :: (() -> Number) -> (() -> Number) function fIncrement(f) { return () => f() + 1; } fIncrement(zero); // [Function]
Agora você pode respirar com calma. A catástrofe é evitada. Continuamos o estudo. Graças a essas duas funções, podemos criar um monte de "números possíveis":
const fOne = fIncrement(zero); const fTwo = fIncrement(one); const fThree = fIncrement(two);
Além disso, podemos criar muitas funções cujos nomes começarão com
f
(vamos chamá-los de funções
f*()
), projetados para trabalhar com "números possíveis":
// fMultiply :: (() -> Number) -> (() -> Number) -> (() -> Number) function fMultiply(a, b) { return () => a() * b(); } // fPow :: (() -> Number) -> (() -> Number) -> (() -> Number) function fPow(a, b) { return () => Math.pow(a(), b()); } // fSqrt :: (() -> Number) -> (() -> Number) function fSqrt(x) { return () => Math.sqrt(x()); } const fFour = fPow(fTwo, fTwo); const fEight = fMultiply(fFour, fTwo); const fTwentySeven = fPow(fThree, fThree); const fNine = fSqrt(fTwentySeven); // , . !
Viu o que fizemos aqui? Com "números possíveis", você pode fazer o mesmo que com números comuns. Os matemáticos chamam isso de
isomorfismo . Um número comum sempre pode ser transformado em um "número possível", colocando-o em uma função. Você pode obter o "número possível" chamando a função. Em outras palavras, temos um mapeamento entre números regulares e "números possíveis". Na verdade, isso é muito mais interessante do que parece. Em breve retornaremos a essa ideia.
A técnica acima, usando a função wrapper, é uma estratégia válida. Podemos nos esconder atrás das funções, tanto quanto necessário. E, como ainda não chamamos nenhuma dessas funções, todas elas, teoricamente, são puras. E ninguém começa uma guerra. No código regular (não relacionado a foguetes), na verdade precisamos de efeitos colaterais no final. Agrupar tudo o que precisamos em uma função nos permite controlar com precisão esses efeitos. Escolhemos a hora em que esses efeitos aparecem.
Deve-se notar que não é muito conveniente usar construções uniformes com montes de colchetes em todos os lugares para declarar funções. E criar novas versões de cada função também não é uma atividade agradável. O JavaScript possui ótimas funções
Math.sqrt()
como
Math.sqrt()
. Seria ótimo se houvesse uma maneira de usar essas funções comuns com nossos "valores pendentes". Na verdade, falaremos sobre isso agora.
Efeito Functor
Aqui falaremos sobre functores representados por objetos que contêm nossas "funções adiadas". Para representar o functor, usaremos o objeto
Effect
. Vamos colocar nossa função
fZero()
em um objeto desse tipo. Mas, antes de fazer isso, tornaremos essa função um pouco mais segura:
// zero :: () -> Number function fZero() { console.log('Starting with nothing'); // , , . // . return 0; }
Agora, descrevemos a função construtora para criar objetos do tipo
Effect
:
// Effect :: Function -> Effect function Effect(f) { return {}; }
Não há nada particularmente interessante aqui, portanto, trabalharemos nesse recurso. Então, queremos usar a função
fZero()
usual com o objeto
Effect
. Para fornecer esse cenário, escreveremos um método que aceita uma função regular e um dia o aplicará ao nosso "valor pendente". E faremos isso sem chamar a função
Effect
. Chamamos esse
map()
função
map()
. Ele tem esse nome devido ao fato de criar um mapeamento entre a função usual e a função
Effect
. Pode ser assim:
// Effect :: Function -> Effect function Effect(f) { return { map(g) { return Effect(x => g(f(x))); } } }
Agora, se você acompanhar de perto o que está acontecendo, poderá ter perguntas sobre a função
map()
. Parece suspeitamente semelhante à música. Voltaremos a esse problema mais tarde, mas por enquanto testaremos o que temos no momento em ação:
const zero = Effect(fZero); const increment = x => x + 1;
Então ... Agora não temos a oportunidade de observar o que aconteceu aqui. Portanto, vamos modificar o
Effect
para, por assim dizer, ter a oportunidade de "puxar o gatilho":
// Effect :: Function -> Effect function Effect(f) { return { map(g) { return Effect(x => g(f(x))); }, runEffects(x) { return f(x); } } } const zero = Effect(fZero); const increment = x => x + 1; // . const one = zero.map(increment); one.runEffects(); // // 1
Se necessário, podemos continuar chamando a função
map()
:
const double = x => x * 2; const cube = x => Math.pow(x, 3); const eight = Effect(fZero) .map(increment) .map(double) .map(cube); eight.runEffects();
Aqui, o que está acontecendo já está começando a se tornar mais interessante. Chamamos isso de "functor". Tudo isso significa que o objeto
Effect
tem uma função
map()
e obedece a algumas
regras . No entanto, essas não são regras que proíbem qualquer coisa. Essas regras são sobre o que você pode fazer. Eles são mais como privilégios. Como o objeto
Effect
é um functor, ele obedece a essas regras. Em particular, esta é a chamada "regra de composição".
É assim:
Se houver um objeto
Effect
chamado
e
e duas funções,
f
e
g
,
e.map(g).map(f)
equivalente a
e.map(x => f(g(x)))
.
Em outras palavras, dois métodos
map()
consecutivos são equivalentes a compor duas funções. Isso significa que um objeto do tipo
Effect
pode executar ações semelhantes às seguintes (lembre-se de um dos exemplos acima):
const incDoubleCube = x => cube(double(increment(x)))
Quando fazemos o que é mostrado aqui, temos a garantia de obter o mesmo resultado que obteríamos usando uma versão desse código com uma chamada tripla para
map()
. Podemos usar isso ao refatorar o código e podemos ter certeza de que o código funcionará corretamente. Em alguns casos, alterar uma abordagem para outra pode até melhorar o desempenho.
Agora, proponho parar de experimentar números e falar sobre o que parece mais com o código usado em projetos reais.
▍Método de ()
O construtor do objeto
Effect
aceita, como argumento, uma função. Isso é conveniente, pois a maioria dos efeitos colaterais que queremos adiar são funções. Por exemplo, estes são
Math.random()
e
console.log()
. No entanto, às vezes você precisa colocar um valor em um objeto
Effect
que não seja uma função. , ,
window
. , . , ( -, , , Haskell
pure
):
// of :: a -> Effect a Effect.of = function of(val) { return Effect(() => val); }
, , , -. , , . HTML- . , . . Por exemplo:
window.myAppConf = { selectors: { 'user-bio': '.userbio', 'article-list': '#articles', 'user-name': '.userfullname', }, templates: { 'greet': 'Pleased to meet you, {name}', 'notify': 'You have {n} alerts', } };
,
Effect.of()
,
Effect
:
const win = Effect.of(window); userBioLocator = win.map(x => x.myAppConf.selectors['user-bio']);
▍ Effect
. ,
Effect
. ,
getElementLocator()
,
Effect
, . DOM,
document.querySelector()
— , . :
// $ :: String -> Effect DOMElement function $(selector) { return Effect.of(document.querySelector(s)); }
, ,
map()
:
const userBio = userBioLocator.map($);
, , .
div
,
map()
, , . ,
innerHTML
, :
const innerHTML = userBio.map(eff => eff.map(domEl => domEl.innerHTML));
, .
userBio
, . , , , . , ,
Effect('user-bio')
. , , , :
Effect(() => '.userbio');
— . :
Effect(() => window.myAppConf.selectors['user-bio']);
,
map()
, ( ). , ,
$
, :
Effect(() => $(window.myAppConf.selectors['user-bio']))
, :
Effect( () => Effect.of(document.querySelector(window.myAppConf.selectors['user-bio']))) );
Effect.of
, :
Effect( () => Effect( () => document.querySelector(window.myAppConf.selectors['user-bio']) ) );
, , , .
Effect
.
▍ join()
? ,
Effect
. , , .
Effect
.runEffect()
. . , - , , , , . , .
join()
.
Effect
,
runEffect()
, . , .
// Effect :: Function -> Effect function Effect(f) { return { map(g) { return Effect(x => g(f(x))); }, runEffects(x) { return f(x); } join(x) { return f(x); } } }
, :
const userBioHTML = Effect.of(window) .map(x => x.myAppConf.selectors['user-bio']) .map($) .join() .map(x => x.innerHTML);
▍ chain()
,
.map()
,
.join()
, . , , . , ,
Effect
. ,
.map()
.join()
. , ,
Effect
:
// Effect :: Function -> Effect function Effect(f) { return { map(g) { return Effect(x => g(f(x))); }, runEffects(x) { return f(x); } join(x) { return f(x); } chain(g) { return Effect(f).map(g).join(); } } }
chain()
- , ,
Effect
( ,
). HTML- :
const userBioHTML = Effect.of(window) .map(x => x.myAppConf.selectors['user-bio']) .chain($) .map(x => x.innerHTML);
-. . ,
flatMap
. , , — , ,
join()
. Haskell, ,
bind
. , - , ,
chain
,
flatMap
bind
— .
▍ Effect
Effect
, . . , DOM, , ? , , , . , . —
.
// tpl :: String -> Object -> String const tpl = curry(function tpl(pattern, data) { return Object.keys(data).reduce( (str, key) => str.replace(new RegExp(`{${key}}`, data[key]), pattern ); });
. :
const win = Effect.of(window); const name = win.map(w => w.myAppConfig.selectors['user-name']) .chain($) .map(el => el.innerHTML) .map(str => ({name: str});
, . . (
name
pattern
)
Effect
.
tpl()
, ,
Effect
.
,
map()
Effect
tpl()
:
pattern.map(tpl);
, .
map()
:
map :: Effect a ~> (a -> b) -> Effect b
:
tpl :: String -> Object -> String
,
map()
pattern
, ( ,
tpl()
)
Effect
.
Effect (Object -> String)
pattern
Effect
. .
Effect
, .
ap()
:
// Effect :: Function -> Effect function Effect(f) { return { map(g) { return Effect(x => g(f(x))); }, runEffects(x) { return f(x); } join(x) { return f(x); } chain(g) { return Effect(f).map(g).join(); } ap(eff) { // - ap, , eff ( ). // map , eff ( 'g') // g, f() return eff.map(g => g(f())); } } }
.ap()
:
const win = Effect.of(window); const name = win.map(w => w.myAppConfig.selectors['user-name']) .chain($) .map(el => el.innerHTML) .map(str => ({name: str})); const pattern = win.map(w => w.myAppConfig.templates('greeting')); const greeting = name.ap(pattern.map(tpl));
, … , ,
.ap()
. , ,
map()
,
ap()
. , , .
. , . , , ,
Effect
,
ap()
. , :
// liftA2 :: (a -> b -> c) -> (Applicative a -> Applicative b -> Applicative c) const liftA2 = curry(function liftA2(f, x, y) { return y.ap(x.map(f)); // : // return x.map(f).chain(g => y.map(g)); });
liftA2()
, , .
liftA3()
:
// liftA3 :: (a -> b -> c -> d) -> (Applicative a -> Applicative b -> Applicative c -> Applicative d) const liftA3 = curry(function liftA3(f, a, b, c) { return c.ap(b.ap(a.map(f))); });
,
liftA2()
liftA3()
Effect
. , ,
ap()
.
liftA2()
:
const win = Effect.of(window); const user = win.map(w => w.myAppConfig.selectors['user-name']) .chain($) .map(el => el.innerHTML) .map(str => ({name: str}); const pattern = win.map(w => w.myAppConfig.templates['greeting']); const greeting = liftA2(tpl)(pattern, user);
?
, , , . ? ,
Effect
ap()
. , ? ?
: « , , ».
:
▍
— . , , , .
const pattern = window.myAppConfig.templates['greeting'];
, , , :
const pattern = Effect.of(window).map(w => w.myAppConfig.templates('greeting'));
— , , , , . . — , , . , , , , , , . , . — . , , , . , .
. .
▍ Effect
, , . -
Facebook
Gmail
. ? .
, . . CSV- . . , , , . , . , . , , , .
, . ,
map()
reduce()
, . . , . , , , . 4 (, , 8, 16, ). , , . , . , - .
, , . , . Não se parece com nada? , , , . . , .
TensorFlow , .
TensorFlow, , . «». , , :
node1 = tf.constant(3.0, tf.float32) node2 = tf.constant(4.0, tf.float32) node3 = tf.add(node1, node2)
Python, JavaScript. ,
Effect
,
add()
, (
sess.run()
).
print("node3: ", node3) print("sess.run(node3): ", sess.run(node3)) # node3: Tensor("Add_2:0", shape=(), dtype=float32) # sess.run(node3): 7.0
, (7.0) ,
sess.run()
. , . , , , .
Sumário
, . , .
Effect
.
, , , , , . , , .
Effect
, , , . , .
— . , . , , . . . , .
Caros leitores! ?