Pensamento funcional. Parte 5

Em um post anterior sobre currying, vimos como funções com vários parâmetros são divididas em funções menores, com um parâmetro. Essa é uma solução matematicamente correta, mas há outras razões para fazê-lo - também leva a uma técnica muito poderosa chamada aplicação parcial de funções . Esse estilo é muito usado em programação funcional e é muito importante entendê-lo.




Uso parcial de funções


A idéia de uma aplicação parcial é que, se fixarmos os primeiros N parâmetros da função, obteremos uma nova função com os demais parâmetros. A partir da discussão sobre o curry, pode-se ver como a aplicação parcial ocorre naturalmente.
Alguns exemplos simples para ilustrar:


//  ""       +  42 let add42 = (+) 42 //    add42 1 add42 3 //       //      [1;2;3] |> List.map add42 //          "" let twoIsLessThan = (<) 2 //   twoIsLessThan 1 twoIsLessThan 3 //      twoIsLessThan [1;2;3] |> List.filter twoIsLessThan //   ""       printfn let printer = printfn "printing param=%i" //      printer    [1;2;3] |> List.iter printer 

Em cada caso, criamos uma função parcialmente aplicada que pode ser reutilizada em diferentes situações.


E, é claro, a aplicação parcial facilita igualmente a correção de parâmetros de função. Aqui estão alguns exemplos:


 //    List.map let add1 = (+) 1 let add1ToEach = List.map add1 //   "add1"  List.map //  add1ToEach [1;2;3;4] //    List.filter let filterEvens = List.filter (fun i -> i%2 = 0) //    //  filterEvens [1;2;3;4] 

O exemplo a seguir, mais complexo, ilustra como a mesma abordagem pode ser usada para criar transparentemente o comportamento "incorporado".


  • Criamos uma função que soma dois números, mas, além disso, é necessária uma função de registro que registrará esses números e o resultado.
  • A função de registro utiliza dois parâmetros: (string) "nome" e (genérico) "valor"; portanto, possui a string->'a->unit assinatura string->'a->unit .
  • Em seguida, criamos várias implementações da função de registro, como um registrador de console ou um registrador baseado em pop-up.
  • E, finalmente, aplicamos parcialmente a função principal para criar uma nova função, com um registrador fechado.

 //      - let adderWithPluggableLogger logger xy = logger "x" x logger "y" y let result = x + y logger "x+y" result result //  -      let consoleLogger argName argValue = printfn "%s=%A" argName argValue //           let addWithConsoleLogger = adderWithPluggableLogger consoleLogger addWithConsoleLogger 1 2 addWithConsoleLogger 42 99 //  -      let popupLogger argName argValue = let message = sprintf "%s=%A" argName argValue System.Windows.Forms.MessageBox.Show( text=message,caption="Logger") |> ignore //    -       let addWithPopupLogger = adderWithPluggableLogger popupLogger addWithPopupLogger 1 2 addWithPopupLogger 42 99 

Essas funções do registrador fechado podem ser usadas como qualquer outra função. Por exemplo, podemos criar um aplicativo parcial para adicionar 42 e depois passá-lo para a função list, como fizemos para a função add42 simples.


 //         42 let add42WithConsoleLogger = addWithConsoleLogger 42 [1;2;3] |> List.map add42WithConsoleLogger [1;2;3] |> List.map add42 //     

Funções parcialmente aplicadas são uma ferramenta muito útil. Podemos criar funções de biblioteca flexíveis (embora complexas), e é fácil torná-las reutilizáveis ​​por padrão, para que a complexidade fique oculta do código do cliente.


Design de Função Parcial


Obviamente, a ordem dos parâmetros pode afetar seriamente a conveniência do uso parcial. Por exemplo, a maioria das funções na List , como List.map e List.filter tem uma forma semelhante, a saber:


 List-function [function parameter(s)] [list] 

A lista é sempre o último parâmetro. Alguns exemplos em forma completa:


 List.map (fun i -> i+1) [0;1;2;3] List.filter (fun i -> i>1) [0;1;2;3] List.sortBy (fun i -> -i ) [0;1;2;3] 

Os mesmos exemplos usando aplicação parcial:


 let eachAdd1 = List.map (fun i -> i+1) eachAdd1 [0;1;2;3] let excludeOneOrLess = List.filter (fun i -> i>1) excludeOneOrLess [0;1;2;3] let sortDesc = List.sortBy (fun i -> -i) sortDesc [0;1;2;3] 

Se as funções da biblioteca fossem implementadas com uma ordem diferente de argumentos, a aplicação parcial seria muito menos conveniente.


Quando você escreve sua função com muitos parâmetros, pode pensar em sua melhor ordem. Como em todos os problemas de design, não há resposta "certa", mas existem várias recomendações geralmente aceitas.


  1. Comece com opções que provavelmente serão estáticas.
  2. Seja o último a definir estruturas ou coleções de dados (ou outros parâmetros variáveis)
  3. Para uma melhor compreensão de operações como subtração, é aconselhável observar a ordem esperada

A primeira dica é simples. Os parâmetros que provavelmente serão "corrigidos" por aplicação parcial devem ir primeiro, como nos exemplos com o criador de logs acima.


Seguir a segunda dica facilita o uso do operador e da composição da tubulação. Já vimos isso muitas vezes nos exemplos com funções acima das listas.


 //          let result = [1..10] |> List.map (fun i -> i+1) |> List.filter (fun i -> i>5) 

Da mesma forma, as funções parciais aplicadas nas listas são facilmente expostas à composição, porque O parâmetro list pode ser omitido:


 let compositeOp = List.map (fun i -> i+1) >> List.filter (fun i -> i>5) let result = compositeOp [1..10] 

Invólucro parcial da função BCL


As funções da biblioteca básica de classes .NET (BCL) do .NET são facilmente acessíveis a partir do F #, mas foram projetadas sem depender de linguagens funcionais, como o F #. Por exemplo, a maioria das funções requer um parâmetro de dados no início, enquanto em F # o parâmetro de dados geralmente deve ser o último.


No entanto, você pode escrever facilmente wrappers para tornar essas funções mais idiomáticas. No exemplo abaixo, as funções de cadeia do .NET são reescritas para que a cadeia de destino seja usada por último e não primeiro:


 //     .NET  let replace oldStr newStr (s:string) = s.Replace(oldValue=oldStr, newValue=newStr) let startsWith lookFor (s:string) = s.StartsWith(lookFor) 

Depois que a string se tornar o último parâmetro, você poderá usar estas funções em pipelines, como de costume:


 let result = "hello" |> replace "h" "j" |> startsWith "j" ["the"; "quick"; "brown"; "fox"] |> List.filter (startsWith "f") 

ou na composição de funções:


 let compositeOp = replace "h" "j" >> startsWith "j" let result = compositeOp "hello" 

Entendendo o operador do transportador


Depois de ver uma aplicação parcial nos negócios, você pode entender como as funções em pipeline funcionam.


A função de pipelining é definida da seguinte maneira:


 let (|>) xf = fx 

Tudo o que ela faz é colocar um argumento antes da função, não depois.


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

No caso em que a função f possui vários parâmetros, e o último parâmetro da função f atuará como o valor de entrada x pipeline. De fato, a função transferida f já foi parcialmente aplicada e espera apenas um parâmetro - o valor de entrada para a tubulação (te x ).


Aqui está um exemplo semelhante reescrito para uso parcial.


 let doSomething xy = let intermediateFn z = x+y+z intermediateFn //  intermediateFn let doSomethingPartial = doSomething 1 2 doSomethingPartial 3 //       3 |> doSomethingPartial //    ,        

Como você viu, o operador em pipeline é extremamente comum em F # e é usado sempre que você deseja preservar o fluxo natural de dados. Mais alguns exemplos que você pode encontrar:


 "12" |> int //   "12"  int 1 |> (+) 2 |> (*) 3 //   

Operador de transportador reverso


De tempos em tempos, você pode encontrar o operador de pipeline inverso "<|".


 let (<|) fx = fx 

Essa função parece não fazer nada, então por que existe?


O motivo é que, quando o operador de pipeline inverso é usado como um operador binário no estilo infix, reduz a necessidade de parênteses, o que torna o código mais limpo.


 printf "%i" 1+2 //  printf "%i" (1+2) //   printf "%i" <| 1+2 //    

Você pode usar pipelines em duas direções ao mesmo tempo para obter a notação de pseudo-infixo.


 let add xy = x + y (1+2) add (3+4) //  1+2 |> add <| 3+4 //   

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/pt430622/


All Articles