Hackear um lambda multi-statement não é tão ruim quanto o Pyrospade faz: com certeza nós poderíamos compor um monte de funções monádicas usando bind, como em Haskell, mas já que estamos o mundo impuro do Python, podemos também usar efeitos colaterais para alcançar a mesma coisa.
Eu cubro algumas maneiras de fazer isso no meu blog .
Por exemplo, o Python garante avaliar os elementos de uma tupla em ordem, então podemos usar ,
como um imperativo ;
. Podemos substituir muitas instruções, como print
, por expressões, como sys.stdout.write
.
Por isso, os seguintes são equivalentes:
def print_in_tag_def(tag, text):
print "<" + tag + ">"
print text
print "</" + tag + ">"
import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
print_(text),
print_("</" + tag + ">"),
None)[-1]
Observe que adicionei um None
no final e extraí-lo usando [-1]
; isso define o valor de retorno explicitamente. Não precisamos fazer isso, mas sem isso teríamos um valor de retorno (None, None, None)
, que podemos ou não nos importar.
Assim, podemos sequenciar ações IO. E quanto às variáveis locais?
O =
do Python forma uma declaração, por isso precisamos encontrar uma expressão equivalente. Uma maneira é alterar o conteúdo da estrutura de dados, passado como um argumento. Por exemplo:
def stateful_def():
foo = 10
bar = foo * foo
foo = 2
return foo + bar
stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
state.setdefault('bar', state.get('foo') * state.get('foo')),
state.pop('foo'),
state.setdefault('foo', 2),
state.get('foo') + state.get('bar'))[-1])({})
Existem alguns truques sendo usados em stateful_lambda
:
- O argumento
*_
permite que o nosso lambda receba qualquer número de argumentos. Como isso permite os argumentos zero , recuperamos a convenção de chamada de stateful_def
.
- Chamar um argumento
_
é apenas uma convenção que diz "não vou usar essa variável"
- Temos uma função ("wrapper") que retorna outra função ("main"):
lambda state: lambda *_: ...
- Graças ao escopo léxico , o argumento da primeira função estará dentro do escopo do segunda função
- Aceitar alguns argumentos agora e retornar outra função para aceitar o restante depois é conhecido como curry
- Nós imediatamente chamamos a função "wrapper", passando um dicionário vazio:
(lambda state: ...)({})
- Isso nos permite atribuir uma variável
state
a um valor {}
sem usar uma instrução de atribuição (por exemplo, state = {}
)
- Tratamos chaves e valores em
state
como nomes de variáveis e valores vinculados
- Isso é menos complicado do que usar imediatamente chamado lambdas
- Isso nos permite alterar os valores das variáveis
- Usamos
state.setdefault(a, b)
em vez de a = b
e state.get(a)
em vez de a
- Usamos uma tupla para encadear nossos efeitos colaterais, como antes
- Usamos
[-1]
para extrair o último valor, que age como uma declaração return
É claro que isso é bastante complicado, mas podemos criar uma API mais agradável com funções auxiliares:
# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)
# Returns the 'get' and 'setdefault' methods of a new dictionary
mkEnv = lambda *_: callWith({},
lambda d: (d.get,
lambda k, v: (d.pop(k), d.setdefault(k, v))))
# A helper for providing a function with a fresh 'get' and 'setdefault'
inEnv = lambda f: callWith(mkEnv(), f)
# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)
# Uses 'get' and 'set'(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
set('bar', get('foo') * get('foo')),
set('foo', 2),
get('foo') + get('bar'))[-1])