I cover a few ways to do this aton http://chriswarbo.net/index.php?page=news&type=view&id=admin-s-blog%2Fanonymous-closures-inmy blog.
For example, Python guarantees to evaluate the elements of a tuple in order, so we can use ",", much like an imperative ";";. We can replace many statements, like "print"print, with expressions, like "sys.stdout.write"sys.stdout.write.
Note that I've added a "None"None at the end, and extracted it using "[-1]";[-1]; this sets the return value explicitly. We don't have to do this, but without it we'd get a funky "(None, None, None)"(None, None, None) return value, which we may or may not care about.
Python's "="= forms an statement, so we need to find an equivalent expression. One way is to mutate the contents of datastructure, passed in as an argument. For example:
There are few tricks being used in stateful_lambda:
- The
*_argument allows our lambda to take any number of arguments. Since this allows zero arguments, we recover the calling convention ofstateful_def. - Calling an argument
_is just a convention which says "I'm not going to use this variable" - We have one ("wrapper") function returning another ("main") function:
lambda state: lambda *_: ... - Thanks to lexical scope, the argument of the first function will be in-scope for the second function
- Accepting some arguments now and returning another function to accept the rest later is known as currying
- We immediately call the "wrapper" function, passing it an empty dictionary:
(lambda state: ...)({}) - This lets us assign a variable
stateto a value{}without using an assignment statement (eg.state = {}) - We treat keys and values in
stateas variable names and bound values - This is less cumbersome than using immediately-called lambdas
- This allows us to mutate the values of variables
- We use
state.setdefault(a, b)instead ofa = bandstate.get(a)instead ofa - We use a tuple to chain together our side-effects, like before
- We use
[-1]to extract the last value, which acts like areturnstatement
Of course, this is pretty cumbersome, but we can make a nicer API with helper functions:
# 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 = inEnvdelay(inEnv, lambda get, set: (set('foo', 10), set('bar', get('foo') * get('foo')), set('foo', 2), get('foo') + get('bar'))[-1])