Pensamento funcional. Parte 6

Continuamos nossa série de artigos sobre programação funcional em F #. Hoje falamos sobre associatividade e composição de funções, além de comparar a composição e o pipeline. Olhe embaixo do gato!




Associatividade e composição de funções


Associatividade da função


Suponha que exista uma cadeia de funções escritas em uma linha. Em que ordem eles serão combinados?


Por exemplo, o que essa função significa?


let F xyz = xyz 

Isso significa que a função y deve ser aplicada ao argumento z e o resultado deve ser passado para x ? Ou seja:


 let F xyz = x (yz) 

Ou a função x aplicada ao argumento y , após o qual a função obtida como resultado será avaliada com o argumento z ? Ou seja:


 let F xyz = (xy) z 

  1. A segunda opção está correta.
  2. O uso de funções deixou associatividade .
  3. xyz significa o mesmo que (xy) z .
  4. E wxyz é igual a ((wx) y) z .
  5. Isso não deve parecer incrível.
  6. Já vimos como o aplicativo parcial funciona.
  7. Se falarmos de x como uma função com dois parâmetros, (xy) z é o resultado de uma aplicação parcial do primeiro parâmetro, seguido pela passagem do argumento z para a função intermediária.

Se você precisar de associatividade correta, poderá usar parênteses ou tubo. As três entradas a seguir são equivalentes:


 let F xyz = x (yz) let F xyz = yz |> x //    let F xyz = x <| yz //    

Como exercício, tente exibir as assinaturas dessas funções sem computação real.


Função Composição


Mencionamos a composição de funções várias vezes, mas o que esse termo realmente significa? Parece assustador à primeira vista, mas, de fato, tudo é bastante simples.


Digamos que temos uma função "f" que mapeia o tipo "T1" para o tipo "T2". Também temos uma função "g" que converte o tipo "T2" em "T3". Em seguida, podemos conectar a saída de "f" e a entrada de "g", criando uma nova função que converte o tipo "T1" no tipo "T3".



Por exemplo:


 let f (x:int) = float x * 3.0 // f  -  int->float let g (x:float) = x > 4.0 // g  -  float->bool 

Podemos criar uma nova função "h" que pega a saída de "f" e a usa como entrada para "g".


 let h (x:int) = let y = f(x) g(y) //    g 

Um pouco mais compacto:


 let h (x:int) = g ( f(x) ) // h    int->bool // h 1 h 2 

Até agora, tão simples. Isso é interessante, podemos definir uma nova função "compor", que pega as funções "f" e "g" e as combina sem nem mesmo saber suas assinaturas.


 let compose fgx = g ( f(x) ) 

Após a execução, você pode ver que o compilador decidiu corretamente que " f " é uma função do tipo genérico 'a para o tipo genérico 'b e ' g ' é limitado à entrada do tipo 'b :


 val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c 

(Observe que uma composição generalizada de operações é possível apenas porque cada função possui exatamente um parâmetro de entrada e uma saída. Essa abordagem não é possível em linguagens não funcionais.)


Como podemos ver, essa definição é usada para o operador " >> ".


 let (>>) fgx = g ( f(x) ) 

Graças a esta definição, novas funções podem ser construídas com base nas funções existentes usando composição.


 let add1 x = x + 1 let times2 x = x * 2 let add1Times2 x = (>>) add1 times2 x // add1Times2 3 

A gravação explícita é muito complicada. Mas você pode facilitar o seu uso.


Primeiramente, você pode se livrar do parâmetro x , e a composição retornará um aplicativo parcial.


 let add1Times2 = (>>) add1 times2 

Em segundo lugar, porque >> é um operador binário, você pode colocá-lo no centro.


 let add1Times2 = add1 >> times2 

Usar composição torna o código mais limpo e claro.


 let add1 x = x + 1 let times2 x = x * 2 //   let add1Times2 x = times2(add1 x) //   let add1Times2 = add1 >> times2 

Usando o operador de composição na prática


O operador de composição (como todos os operadores infix) tem uma prioridade mais baixa que as funções regulares. Isso significa que as funções usadas na composição podem ter argumentos sem usar parênteses.


Por exemplo, se as funções "adicionar" e "horários" tiverem parâmetros, elas poderão ser transmitidas durante a composição.


 let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 let add5Times3 = add 5 >> times 3 // add5Times3 1 

Desde que as entradas e saídas correspondentes das funções correspondam, as funções podem usar qualquer valor. Por exemplo, considere o seguinte código que executa uma função duas vezes:


 let twice f = f >> f // ('a -> 'a) -> ('a -> 'a) 

Observe que o compilador inferiu que " f " aceita e retorna valores do mesmo tipo.


Agora considere a função " + ". Como vimos anteriormente, a entrada é int , mas a saída é realmente (int->int) . Assim, " + " pode ser usado em " twice ". Portanto, você pode escrever:


 let add1 = (+) 1 //  (int -> int) let add1Twice = twice add1 //    (int -> int) // add1Twice 9 

Por outro lado, você não pode escrever:


 let addThenMultiply = (+) >> (*) 

Como a entrada "*" deve ser uma função int , não uma função int->int (que é a saída da adição).


Mas se você corrigir a primeira função para que ela retorne apenas int , tudo funcionará:


 let add1ThenMultiply = (+) 1 >> (*) // (+) 1   (int -> int)   'int' // add1ThenMultiply 2 7 

A composição também pode ser executada na ordem inversa por meio de " << ", se necessário:


 let times2Add1 = add 1 << times 2 times2Add1 3 

A composição reversa é usada principalmente para tornar o código mais parecido com o inglês ("semelhante ao inglês"). Por exemplo:


 let myList = [] myList |> List.isEmpty |> not //   myList |> (not << List.isEmpty) //    

Composição vs. transportador


Você pode ficar confuso com a pequena diferença entre a composição e o transportador, como eles podem parecer muito semelhantes.


Primeiro, observe a definição de um pipeline:


 let (|>) xf = fx 

Tudo isso permite que você coloque os argumentos das funções antes e não depois. Isso é tudo. Se a função tiver vários parâmetros, a entrada deverá ser o último parâmetro (no conjunto atual de parâmetros, e de maneira nenhuma). Um exemplo visto anteriormente:


 let doSomething xyz = x+y+z doSomething 1 2 3 //      3 |> doSomething 1 2 //      

A composição não é a mesma e não pode substituir um tubo. No exemplo a seguir, mesmo o número 3 não é uma função, portanto, a "saída" não pode ser passada para doSomething :


 3 >> doSomething 1 2 //  // f >> g      g(f(x))     : doSomething 1 2 ( 3(x) ) //   3   ! // error FS0001: This expression was expected to have type 'a->'b // but here has type int 

O compilador reclama que o valor "3" deve ser um tipo de função 'a->'b .


Compare isso com a definição de composição, que usa 3 argumentos, onde os dois primeiros devem ser funções.


 let (>>) fgx = g ( f(x) ) let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 

Tentativas de usar o pipeline em vez da composição resultarão em um erro de compilação. No exemplo a seguir, " add 1 " é a função (parcial) int->int , que não pode ser usada como o segundo parâmetro para " times 2 ".


 let add1Times2 = add 1 |> times 2 //  // x |> f      f(x)     : let add1Times2 = times 2 (add 1) // add1   'int' // error FS0001: Type mismatch. 'int -> int' does not match 'int' 

O compilador reclama que " times 2 " deve aceitar o parâmetro int->int , ou seja, ser uma função (int->int)->'a .


Recursos Adicionais


Existem muitos tutoriais para F #, incluindo materiais para quem vem com experiência em C # ou Java. Os links a seguir podem ser úteis à medida que você avança no F #:



Várias outras maneiras de começar a aprender F # também são descritas.


Finalmente, a comunidade F # é muito amigável para iniciantes. Há um bate-papo muito ativo no Slack, suportado pela F # Software Foundation, com salas para iniciantes nas quais você pode participar livremente . É altamente recomendável que você faça isso!


Não se esqueça de visitar o site da comunidade de língua russa F # ! Se você tiver alguma dúvida sobre o aprendizado de um idioma, teremos prazer em discuti-los nas salas de bate-papo:



Sobre autores de tradução


Traduzido por @kleidemos
As mudanças de tradução e editoriais foram feitas pelos esforços da comunidade de desenvolvedores de F # de língua russa . Agradecemos também a @schvepsss e @shwars pela preparação deste artigo para publicação.

Source: https://habr.com/ru/post/pt413195/


All Articles