CollabCodeTech / forum-do-front-ao-end

Fórum da turma do curso do Front ao End
81 stars 10 forks source link

Entendendo o que é armazenado em uma variável quando se trata de uma função aninhada #109

Open ghost opened 5 years ago

ghost commented 5 years ago
function multiplier(factor) {
  return number => number * factor;
}

let twice = multiplier(2);
console.log(twice);
/*
function (number) {
    return number * factor;
}
*/
console.log(twice(5)); // 10

Bom dia. Alguém poderia me ajudar a entender por que registrar no console o valor de twice mostra a função que é retornada com factor, e não com o argumento 2, como em multiplier(2)? Entendo quando é registrado twice(5), em que o valor retornado é 10, mas não entendo esse primeiro caso.

Pliavi commented 5 years ago

Então, esse esquema de função aninhada é conhecido como High-Order function, significa que o retorno da função é outra função, por isso ele retorna um fragmento de função no console.

Fonte: Understanding Higher-Order Functions in JavaScript Tá em inglês, mas é uma boa, ele fala bastante do caso do map,filter e companhia, mas no final fala sobre as customizadas.

O motivo de ele mostrar a função com o nome factor e não com o 2 é por ser uma função, ele vai mostrar como ela foi escrita, e não como será executada, até porque ela realmente não foi, é um esquema chamado de lazy evaluation, a função só será executada quando ela for realmente necessária, você gerou uma nova função, mas como ela não foi executada, o factor ainda não foi passado pra ela.

Posso estar muito errado, é o que vejo em algumas linguagens voltadas pro lado funcional, dei uma pesquisada por JS e não achei muito sobre o assunto (específico para as funções), mas creio que seja isso. Segue um artigo falando sobre, em Scala: Non Strict Lazy Evaluations in Functional Programming

ghost commented 5 years ago

Pliavi, resposta relâmpago. Obrigado!

Vou ler os artigos que mandou em seguida, mas antes, compartilho que vi esse programa no livro Eloquent JavaScript, na parte sobre closure. Lá, o autor diz o seguinte:

In the example, multiplier is called and creates an environment in which its factor parameter is bound to 2. The function value it returns, which is stored in twice, remembers this environment. So when that is called, it multiplies its argument by 2.

No exemplo, multiplier é chamada e cria um ambiente no qual seu parâmetro factor é ligado a 2. O valor de função que retorna, que é armazenado em twice, se lembra desse ambiente. Então, quando esse é chamado, multiplica seu argumento por 2.

Multiplier é chamada somente quando console.log(twice(5)); é executado? Antes, o que está armazenado no twice? É o retorno da função, o que ele chama de "function value"? Qual é tipo de dado armazenado, é uma função mesmo?

Pliavi commented 5 years ago

Não exatamente, em console.log(twice(5)) o multiplier já foi executado retornando a função anônima (arrow function) descrita, tanto que se você alterar a função multiplier, o funcionamento de twice continua o antigo, é como dito no fragmento que você colocou, o ambiente é mantido (lembrado) junto com o número 2, mas só é executado quando twice é chamado.

Isso, o que está dentro de twice é uma função, a que multiplier retorna. Você pode verificar usando o comando typeof

image Ambos, twice e multiplier, são funções.

Pior que essas coisas dão um nó na cabeça xD

ghost commented 5 years ago

Veja se entendi, por favor. twice funciona como uma referência à função aninhada e a seu ambiente? Somente são utilizados quando a função twice é executada, porque JavaScript é uma linguagem que trabalha com lazy evalutation?

ghost commented 5 years ago

Marquei algumas partes desse fragmento sobre escopo léxico, no Wikipédia, que parecem ter ligação com essa ideia de closure, e com lazy evaluation:

O escopo léxico ou estático foi introduzido pela linguagem ALGOL 60. O escopo é assim denominado, porque pode ser determinado estaticamente, ou seja, antes da execução. O escopo léxico define o escopo em termos da estrutura léxica do programa. Com escopo léxico, um nome sempre se refere ao seu ambiente léxico (mais ou menos) local. Esta é uma propriedade do texto do programa e é feita independente da pilha de chamadas em tempo de execução pela implementação da linguagem. Ou seja, O escopo léxico de uma declaração é a parte do texto do programa, onde a utilização do identificador é uma referência a essa declaração particular do identificador. Pelo fato de esta correspondência só exigir a análise do texto do programa estático, este tipo de delimitação de escopo é também chamado de escopo estático.

Pliavi commented 5 years ago

Veja se entendi, por favor. twice funciona como uma referência à função aninhada e a seu ambiente?

Pior que tá entrando outro termo técnico aqui... Não é uma referência, pois uma referência é algo (variável) que aponta para um espaço de memória, e mais de uma variável pode ter essa referencia, sendo assim, qaulquer que você alterar, vai alterar as outras, pois todos apontam para o mesmo valor.

Porém o javascript passa tudo por valor, não existe referência, digo, existe, mas apenas para objetos (arrays e funções são objetos), mas não utilizando o operador '=' ou passando como parâmetro de função, sempre será por valor ao invés de referência.

Cada vez que multiplier é chamada uma nova função anônima é criada e retornada. No exemplo que você deu, o 2 está lá, mas está implícito apenas.

porque JavaScript é uma linguagem que trabalha com lazy evalutation?

Lazy evaluation não é uma característica da linguagem, é uma estratégia, uma forma de escrever o código para que ele execute apenas as tarefas necessárias. As linguagens funcionais tendem a ter um suporte maior a isso fazendo com que esse trabalho seja mais simples, mas não é algo que faça parte da linguagem em si. \ Falei linguagens, mas se pá a única que sei que faz isso é Haskell hehe :v

Marquei algumas partes desse fragmento sobre escopo léxico Por aí, mas não tem muita ligação, o escopo lexico define que um identificador (eg. nome de variavel) pertence a um escopo X e todos os ambientes ou escopos dentro dele tem acesso a ele, porém não o contrário já que é estático e é criado assim que o código é lido. Ex:


let x = 10; // escopo global

function teste() { x = x * 2; }

function teste2() { let x = 50; console.log(x); }

console.log(x) // 10 teste(); console.log(x) // 20 :: alterou o valor de x pra 20 teste2(); // 50 :: alterado o valor de x apenas dentro da função, mas não é o mesmo x anterior console.log(x) // 20 :: o valor de x continuou 20


Se ver o `x` está no escopo global, por isso posso usá-lo e alterá-lo dentro de `teste()`, e por conta do `let` eu não posso recriá-lo (usar `let x` novamente)...
Mas veja, eu uso `let x = 50` dentro de `teste2()`, e funciona, porque `x` passa a existir nos dois contextos, mas sendo valores diferentes, pois o `x` dentro da função é um e o de fora é outro por terem sidos criados em contextos diferentes, tanto que o javascript remove a referência de `x` global ao usar o `let` dentro da função `teste2()`.

Ufa! :sweat_smile: 
~espero não ter falado nenhuma besteira~
ghost commented 5 years ago

Avançamos bastante na discussão. Muito obrigado! Vou reler algumas coisas com mais calma, porque foi bastante conteúdo.

ghost commented 5 years ago

Oi @Pliavi e companhia! (Dando uma atualizada na discussão) Achei esse artigo muito bom. Está em inglês, mas acho que é muito útil, especialmente para quem está na aula 32, pois fala de closure, IIFE, e de outras coisas interessantes.

image

Acho que consegui entender um pouco melhor minha dúvida inicial. Parece que na imagem acima, por exemplo, quando counter() é chamada, um ambiente léxico "vazio" (sem variáveis locais) é criado para ela. Porém, toda função, quando criada, recebe uma propriedade chamada [[Environment]], que contém uma referência ao ambiente léxico onde foi criada. Todo ambiente léxico é composto de duas coisas: um registro do ambiente (os retângulos, na imagem), que é um objeto que contém todas as variáveis locais como suas propriedades (e também o valor de this (achei interessante essa info), e uma referência ao ambiente externo (indicada pelas setas, na imagem), geralmente tudo que está fora do par de chaves { } da função.

image

Sendo assim, o [[Environment]] de counter é a referência externa para a chamada counter(). Tudo que está fora das chaves da função aninhada, retornada, em makeCounter é acessível. counter() encontra count entre as variáveis de makeCounter, podendo incrementá-la. Enquanto essa referência existir, o valor de count poderá ser acessado e modificado no "local" (counter). Uma variável diferente, counter2, por exemplo, teria um [[Environment]] totalmente independente do [[Environment]] de counter.

image

O estado inicial é preservado enquanto houver referências a ele, enquanto houver funções que o usem.

Acho que essa ideia de estado inicial privado tem a ver a organização do código do jogo da memória, certo? (Aqui estou arriscando muito no vocabulário) Parece que permite um código mais "modular", com menos repetição de código, certo? Por exemplo, toda uma estrutura complexa pode ser replicada "modularmente"(?) com uma declaração de variável que tem como valor o que é retornado de outra função. Acho que, como você falou, programação funcional parece se basear muito nessa liberdade do JS.

O que acha?