Confira a parte 1 aqui.
Eu estou tentando aprender programação funcional (na verdade, é mais como “a maneira de pensar funcional”). Estou usando Racket para isso e este é o meu segundo artigo sobre a criação de algo para composição de função no estilo da linguagem Factor. Veja a parte 1 para acompanhar.
Minha primeira tentativa pode compor apenas funções com um único argumento; esta versão pode fazê-lo usando mais de um, mas também tem algumas desvantagens.
Composição de função, estilo concatenativa
Agora eu posso fazer algo assim:
(~> (open “some.csv”) (open “another.csv”) combine filter-something)
É como se eu tivesse um pipe de execuções, e nada que não seja utilizado na função é passado para a função seguinte. Algo assim:
(open “some.csv”)-
-(combine)-> -(filter-something)->
(open “another.csv”)-
Compreender isso como um pipe é realmente útil para construir coisas, no entanto, a maneira mais fácil de implementá-lo não é usando pipes, mas com uma stack simples. Cada função obtém aquilo que necessita a partir do stack e coloca os resultados de volta nele.
#lang racket
(provide >>)
(define (>> . procs)
(apply-to-stack '() procs))
(define (apply-to-stack stack procs)
(match procs
['() stack]
[(list a b ...) #:when (procedure? a) (apply-proc stack a b)]
[(list a b ...) (apply-to-stack (cons a stack) b)]))
(define (apply-proc stack proc remaining-procs)
(match (procedure-arity proc)
[(arity-at-least _) (error "multiple arguments")]
[(list a ...) (error "optional arguments")]
[ary (let* ([params (take stack ary)]
[remaining-stack (drop stack ary)]
[new-stack (cons (apply proc params) remaining-stack)])
(apply-to-stack new-stack remaining-procs))]))
Aqui eu estou usando “>>” para invocar a composição (eu descobri que “->” já está tomado por contratos no Racket).
O modo como funciona é bastante simples:
; Define ">>" that simply calls "apply-to-stack" ; using an empty list as first argument and a list ; of procedures as the second one. (define (>> . procs) (apply-to-stack ‘() procs))
(funções são chamadas de “procedimentos” no jargão Racket)
Isso é apenas por conveniência, nenhum trabalho real está sendo feito, mas é uma interface agradável. Falando sobre interface, isso é exatamente o que “provide” no início do arquivo significa: “expor apenas essa função”, todas as outras funções são privativas nesse arquivo.
(define (apply-to-stack stack procs)
(match procs
; No more procs (empty list), we are done!
; Just return the stack.
[‘() stack]
; The first element is a procedure, apply it
; using the values in the stack
[(list a b …) #:when (procedure? a) (apply-proc stack a b)]
; We have something else, just add it to the stack
; and proceed to next procedure in the list
[(list a b …) (apply-to-stack (cons a stack) b)]))
Pattern matching em Racket é realmente poderoso! Essa é apenas a ponta do iceberg. Eu preciso escrever sobre isso mais tarde!
Resumindo, a primeira parte de dentro “[” e “]” é o padrão a ser correspondido, e a segunda parte é o retorno.
A função acima faz coisas básicas: “Já terminamos?”, “Será que temos um procedimento?”. Se a lista de funções contém coisas que não são funções (“procedimentos” no jargão Racket, certo?), assumimos que elas são argumentos, então basta adicioná-las ao pipe, quero dizer, no stack.
A última parte é onde o trabalho pesado acontece, mas é incrivelmente simples, 9 linhas de código:
(define (apply-proc stack proc remaining-procs)
; Pattern matching to rescue (again)
(match (procedure-arity proc)
; Function accepts variable arguments =(
; Can't handle it yet
[(arity-at-least _) (error “multiple arguments”)]
; Optional arguments
; Can't handle them also (yet)
[(list a …) (error “optional arguments”)]
; Fixed number of arguments, now we are talking!
; Take the arguments from the stack to use
; (and reduce stack accordingly)
[ary (let* ([params (take stack ary)]
[remaining-stack (drop stack ary)]
; Stack now is the remaining stack
; plus the function return
[new-stack (cons (apply proc params)
remaining-stack)])
; Finally, continue to the next function,
; but now using the new stack
(apply-to-stack new-stack remaining-procs))]))
Parece complicado? Não é. Temos dois casos, com os quais não podemos lidar (ainda) e, para aquele com o qual lidamos, só levamos os argumentos do topo do stack, aplicamos a função usando-os e vamos para a próxima função.
Conclusão
No geral, esta versão tem apenas 16 linhas de código. Nada mal para simular uma linguagem concatenativa!
Então, eu aprendi sobre composição de função? Acho que sim, pelo menos um pouco. É uma técnica poderosa que eu acho que posso até mesmo usar em linguagens orientadas a objeto. Me lembro de várias situações em que passar algo por meio de um pipeline de instruções iria tornar o código bem mais simples.
Uma nota para Racket
A linguagem tem construções incríveis: macros fáceis, poderoso pattern matching e várias outras coisas incríveis que eu mal toquei. Quanto mais eu uso, mais a afirmação “uma linguagem de programação programável” parece fazer sentido.
Eu deveria escrever sobre isso também 🙂
***
Ronie Uliana faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://medium.com/@ronie/function-composition-part-2-680d9951f3b0#.6xxfco1er



