Criando uma sintaxe mais limpa para o Bind

Olá pessoa!

No post da semana passada falei um pouco sobre o mindset para criarmos um Continuation e também mostrei como fazer a equivalência entre um binding e uma expressão lambda, que tal continuarmos nesse assunto?

É bem importante que você entende a equivalência entre binding e expressões lambda, com isso, ficará muito mais claro entender como (e porque) a sintaxe mostrada funciona.

O post completo pode ser acessado clicando aqui, mas vou fazer uma recaptulação rápida.

O principal ponto é você entender que estas duas implementações, apesar de sintaticamente diferentes, representam a mesma coisa:

//Binding
let funcao =            
    let a = 5
    let b = 10
    let c = a + b
    c

//Lambda
let funcao = 
    5 |> (fun a -> 
        6 |> (fun b ->
            a + b |> (fun c ->
                c)))

Uma das interpretações para entender isso é bastante simples. É claro que você já deve saber que não é possível acessar um valor (como a, por exemplo) antes dele ser criado, certo?

Então, o código abaixo causaria erros:

let funcao=
    Console.WriteLine a
    let a = 5

Até aqui, bem simples. A visão de código que precisamos ter aqui é: imagine que cada declaração de variavel seja um parâmetro para todo o código que acontece depois dele.

Ou seja, quando criamos o valor a, é como se encapsulássemos todo o código restante em uma função que contém o parâmetro a e o valor atribuído à ele é o valor passado por parâmetro.

let funcao =
    let funcao2 a =
        let b = 10
        let c = a + b
        c
    
    funcao2 5

Essa sintaxe é a mesma coisa que a sintaxe mostrada como expressão lambda, mas talvez seja um pouco mais clara para você.

Utilizando este conceito, vimos que é possível inserir código em uma atribuição, afinal, podemos inserir qualquer trecho de código em uma função, se uma atribuição e uma função podem ser equivalentes, então está tudo certo.

Nosso último exemplo em F# era o seguinte:

let funcaoUsandoLet =            
    let a = 5
    let b = 10
    let c = a + b
    c

let continueCom (valor, lambda) =
    printfn "%i" valor
    valor |> lambda

let funcaoUsandoContinuation =
    continueCom (5, fun a ->
    continueCom (10, fun b ->
    continueCom (a + b, fun c -> c)))

Na prática, as duas funções acima representam o mesmo código, mas usando continuation conseguimos inserir um código para escrever os valores no console.

No post passado eu mencionei que podíamos fazer isso utilizando o próprio let, a questão é, como?

Bom, vamos criar um tipo para fazer isso. Este tipo deve conter a função continueCom. Vamos chamá-lo de auditor.

type Auditor() = 
    member this.continueCom (valor, lambda) =
        printfn "%i" valor
        valor |> lambda

Bom, ele é um tipo que pode ser construído e contém a função continueCom criada anteriormente, só guardarmos o método em um local, certo? -Certo.

Essa é a hora que vamos alterar um pouco a sintaxe para o modo mais formal. Não sabemos exatamente qual o valor, por definição, se você encontrar por aí uma função que recebe qualquer coisa, existe uma boa chance desse parâmetro se chamar x.

No caso do parâmetro lambda, bom, sabemos que é uma função, então com bastante frequência você verá esse parâmetro se chamar f. Quem não lembra do famoso “F de X” das aulas de matemática? -Pois é, aqui estamos de novo com o “F de X”.

Vamos autalizar nosso código com os nomes convecionais, embora eu os ache mais confuso. Também vou remover o pipe, transformando valor |> lambda em f x.

type Auditor() = 
    member this.continueCom (x, f) =
        printfn "%i" x
        f x

Mesmo método, certo? Só alteramos alguns nomes. E falando em alterar nomes, vamos alterar o nome do continueCom também.

Dessa vez para um nome que faz mais sentido mesmo. Lembra qual operação estamos tentando substituir com nosso continuation? -Isso mesmo, o Bind.

Vamos colocar exatamente este nome na função.

type Auditor() = 
    member this.Bind (x, f) =
        printfn "%i" x
        f x

Vamos incluir mais uma função aqui, você ainda não precisa se preocupar com o motivo disso.

type Auditor() = 
    member this.Bind (x, f) =
        printfn "%i" x
        f x

    member this.Return(x) =
        x

E pronto! Já podemos utilizar nosso novo let.

Espera aí, como assim?

Eu explico. Utilizando essa nomenclatura de método, o compilador é capaz de identificar e criar atalhos sintáticos para melhorar a visibilidade de nosso código, vamos ver como nosso código está atualmente:

[<EntryPoint>]
let main argv = 
    let auditor = new Auditor()

    auditor.Bind (5, fun a ->
    auditor.Bind (6, fun b ->
    auditor.Bind (a + b, fun c -> c)))
    |> ignore

Vamos substituir os trechos onde utilizamos o Bind por um comando let!. Para fazer isso, precisamos avisar o compilador o escopo do Bind, veja:

let auditor = new Auditor()

auditor{
        //Inserir o código aqui
} |> ignore

Com isso definimos que, dentro do bloco auditor, todo comando let! (não esqueça da exclamação) se torna uma chamada para o método Bind.

let auditor = new Auditor()

auditor{
    let! x = 5
    let! y = 6
    let! z = x + y
} |> ignore

Um bloco deste tipo também é uma expressão, logo, precisamos retornar alguma coisa. Por isso implementamos a função Return. Ela permite utilizarmos a palavra reservada return dentro destes blocos:

auditor{
    let! x = 5
    let! y = 6
    let! z = x + y
    return z
} |> ignore

Na prática, alteramos a foram de escrever nosso método, mas a compilação transformará as duas soluções na mesma coisa. Esta é só uma forma mais familiar de escrevermos código escondendo uma complexidade.

Atenção Um disclaimer importante é: vamos conversar sobre os métodos possíveis e o que é este “bloco” utilizado como exemplo aqui. O tema é relativamente complexo, então vamos seguir um passo de cada vez. Por vezes estou utilizando simplificações para facilitar o entendimento.

Por hoje ficamos por aqui, o que achou?

Me conte nos comentários!

E Até mais!

Sempre vale lembrar que as informações e textos aqui no blog representam minha opinião pessoal, o que pode não ser igual à sua ou de qualquer outra pessoa, incluindo a empresa para qual eu trabalho. Portanto as publicações inseridas aqui estão relacionadas somente a mim.

Assine a Newsletter