sábado, 12 de outubro de 2013

Encadeamento de funções

Poliedro Encadeamento de função é o pattern onde um grupo de funções é encadeado.

Em uma cadeia, sempre a saída de uma função é a entrada da seguinte, o parâmetro inicial é fornecido à primeira função da cadeia, que segue executando as funções, e o resultado da última função é retornado como resultado da cadeia.

Vejamos como criar cadeia.

Python

A forma mais simples simples de encadear funções é simplesmente efetuar a chamada, como o código Python a seguir:
f1 = lambda x: x * 2
f2 = lambda x: x - 1
f3 = lambda x: x // 3

print(f3(f2(f1(8))))

Porém esse código é visualmente confuso e difícil de ser depurado. É conveniente criar uma fábrica (factory) de cadeias:
chain = lambda *funcs: \
    reduce(lambda acc, func: (lambda x: func(acc(x))),
    funcs or [lambda x: x])

the_chain = chain(
    lambda x: x * 2,
    lambda x: x - 1,
    lambda x: x // 3,
)
print(the_chain(8))

Repare que as funções agora estão na ordem em que são executadas:
  1. O número é dobrado;
  2. O resultado da primeira função é decrementado;
  3. O resultado da terceira função é triplicado e retornado.

A função reduce() de Python executa a redução de um map/reduce: o primeiro parâmetro é uma função que recebe o acumulador e o próximo valor, retornando o resultado parcial.

O segundo parâmetro de reduce() é a lista a ser reduzida, no caso, a lista de funções ou, caso nenhuma função tenha sido informada, um função vazia que retorna o próprio parâmetro fornecido.

Com este procedimento, é possível reduzir a lista de funções a uma função representando o encadeamento das funções fornecidas.

Erlang

Em Erlang as coisas também podem ser interessantes.

Criaremos duas funções, uma fábrica a ser exportado (chain/1) e uma função de redução (chain/2) que não deve ser exportada:
-module(chain).
-export([chain/1]).

chain(Funs) when is_list(Funs) ->
    chain(Funs, fun(X) -> X).

chain([], Acc) -> Acc;

chain([Fun|Rest], Acc) when is_function(Fun) ->
    chain(Rest, fun(X) -> Fun(Acc(X)) end).

Você pode então executar no shell:
1> c(chain).
{ok,chain}
2> Fun = chain:chain([
2> fun(X) -> X * 2 end,
2> fun(X) -> X - 1 end,
2> fun(X) -> X div 3 end
2> ]).
#Fun<chain.1.134034448>
3> Fun(8).
5
4> 

Haskell

Haskell por outro lado já possui uma fábrica de cadeias, usando o carácter $, infelizmente na ordem inversa: primeiro as últimas funções a serem executadas, e por último as primeiras.

Então, por exemplo: f1 $ f2 $ f3 x é executado como f1 (f2 (f3 x)).

Crie um módulo:
module Chain where

the_chain :: Int -> Int
the_chain n = (`div` 3) $ (\x -> x - 1) $ (* 2) n

No prompt do ghci execute:
Prelude> :load chain.hs
[1 of 1] Compiling Chain            ( chain.hs, interpreted )
Ok, modules loaded: Chain.
*Chain> the_chain 8
5
*Chain> 

[]’s
Cacilhας, La Batalema