Pular para o conteúdo

Closures e lambdas

Ações como valores: alta ordem, closures, lambdas e composição.

Ações são valores#

Uma ação pode ser guardada numa variável, passada como argumento e devolvida por outra ação:

dataforge
action dobro(x):
    yield x * 2

f := dobro                       # guardada
out f(21)                        # 42
out [1, 2, 3].map(dobro)         # passada

Alta ordem#

dataforge
action aplicar_duas_vezes(fn, valor):
    yield fn(fn(valor))

out aplicar_duas_vezes(dobro, 5)     # 20

Closures#

Uma ação aninhada lembra o escopo onde foi criada:

dataforge
action fabrica_somador(n):
    action somador(x):
        yield x + n          # 'n' vem do escopo de fora
    yield somador

soma10 := fabrica_somador(10)
out soma10(5)                  # 15
out fabrica_somador(3)(4)      # 7 — chamada encadeada

Estado que persiste#

dataforge
action criar_contador(inicio := 0):
    estado := {"valor": inicio}
    action proximo():
        estado["valor"] := estado["valor"] + 1
        yield estado["valor"]
    yield proximo

c1 := criar_contador()
c2 := criar_contador(100)

out c1(), c1(), c1()     # 1 2 3
out c2(), c2()           # 101 102

Cada chamada de criar_contador produz um contador com estado próprio. É o padrão de encapsulamento sem classe.

Lambdas#

Três grafias, todas equivalentes:

dataforge
quadrado := lambda x: x * x
soma := lambda a, b => a + b
constante := lambda: 42

out quadrado(7), soma(3, 4), constante()

Com anotação de tipo, os parênteses são obrigatórios — sem eles, : iniciaria o corpo:

dataforge
lambda (n: Integer): n + 1

Onde usam-se#

dataforge
out [1, 2, 3].map(lambda n: n * n)
out [1, 2, 3, 4].filter(lambda n: n % 2 is 0)
out [1, 2, 3].reduce(lambda a, b: a + b, 0)
out Col.sort_by(itens, lambda i: -i["preco"])

Para lógica de mais de uma linha, prefira uma ação nomeada: ela pode ser testada isoladamente e o nome documenta a intenção.

Composição#

dataforge
action mais_um(x):
    yield x + 1

action dobrar(x):
    yield x * 2

composta := compose(dobrar, mais_um)     # direita para esquerda
encadeada := pipe_fn(mais_um, dobrar)    # esquerda para direita

out composta(5), encadeada(5)            # 12 12

Aplicação parcial#

dataforge
action multiplicar(a, b):
    yield a * b

triplicar := partial(multiplicar, 3)
out triplicar(7)                     # 21
out [1, 2, 3].map(triplicar)         # [3, 6, 9]

Memoização#

dataforge
cache := {}

action fib(n):
    given n smaller 2:
        yield n
    given cache.has(str(n)):
        yield cache[str(n)]
    valor := fib(n - 1) + fib(n - 2)
    cache[str(n)] := valor
    yield valor

out fib(30)     # instantâneo — sem cache seriam milhões de chamadas

A biblioteca também traz memoize pronto em Arcane.Functional.