Programação funcional para efeito colateral do loop

8

Estou tentando entender por que ter uma variável local ou um loop for dentro de uma função não é considerado pura programação funcional.

Dada esta função:

int as_int(char *str)
{
    int acc; /* accumulate the partial result */

    for (acc = 0; isdigit(*str); str++) {
        acc = acc * 10 + (*str - '0');
    }

    return acc;
}

Em que circunstâncias a variável acc seria um efeito colateral? Mesmo em um ambiente simultâneo, cada chamada da função teria sua própria cópia de acc. Portanto, não entendo por que isso não é permitido na programação funcional.

tomatoRadar
fonte
3
Não tem efeitos colaterais. Veja: programmers.stackexchange.com/questions/196112/…
Ben Aaronson
5
as_inté uma função pura, mas o código nela não é puro.
Doval
1
Código puro é geralmente associado a variáveis ​​imutáveis. accé mutável.
Florian Margaine

Respostas:

11

Looping na programação funcional não é feito com instruções de controle, como fore while, é feito com chamadas explícitas para funções como map, foldou recursão - todos os quais envolvem colocando a chamada loop interno dentro de outra função . Se o código do loop alterar variáveis ​​fora do loop, essa função de loop interno manipulará variáveis ​​fora de seu escopo e, portanto, será impura . Portanto, toda a função externa é pura, mas o loop não é. As construções em loop na programação funcional exigem que você explique o estado. A conversão do seu código para algo usando ferramentas de loop de programação funcional revela a impureza:

int as_int(char *str)
{
    int acc = 0; /* accumulate the partial result */

    map(takeWhile(isdigit, str), void function(char *chr) {
      acc = acc * 10 + (chr - '0');
    });

    return acc;
}

(Observação: essa sintaxe é aproximada para transmitir a ideia geral)

Esse código usa uma função interna para o corpo do loop, que deve alterar a variável acc, que está fora do escopo. Isso é impuro - a função do loop interno depende do contexto do loop externo , chamando-o várias vezes com o mesmo caractere terá efeitos colaterais, e a ordem em que você o chama na sequência de caracteres é importante. Na programação funcional, para tornar isso uma função pura, é necessário tornar explícita essa dependência do estado passado entre iterações de loop com fold:

int as_int(char *str)
{
    return fold(takeWhile(isdigit, str), 0, int function(char *chr, int acc) {
      return acc * 10 + (chr - '0');
    });
}

foldusa uma função de dois argumentos para o corpo do loop interno: o primeiro argumento é um item na sequência que foldestá sendo repetida, enquanto o segundo é algum valor que o corpo do loop interno usa para criar resultados parciais. Para a primeira iteração do loop, accé 0, para o segundo, accqualquer que seja a primeira chamada de função do loop interno retornada, para o terceiro, seja qual for o segundo loop interno retornado, e o loop final retorna o resultado de toda a foldexpressão.

Observe que isso não é realmente um problema com o seu código da perspectiva do resto do seu programa - ambas as definições de as_intsão puras. A diferença é que, ao tornar o código do loop interno uma função pura, você pode aproveitar a enorme variedade de ferramentas que a programação funcional oferece para decompor o loop em algo mais declarativo (por exemplo, usando takeWhile, fold, filter, map, etc. etc.)

Jack
fonte
6
+1. Entre as vantagens do uso de funções de alta ordem, como takeWhile, fold, filter, map, (estilo ou seja declarativa) é que você também pensar parada em termos de "Compute algo por posições de memória atualização destrutiva". Dessa maneira, o resultado não depende da história / sequência exata das etapas de cálculo.
Giorgio