Skip to main content
formatted code, added bullet-point explanations
Source Link
Warbo
  • 1.2k
  • 8
  • 12

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 of stateful_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 state to a value {} without using an assignment statement (eg. state = {})
  • We treat keys and values in state as 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 of a = b and state.get(a) instead of a
  • We use a tuple to chain together our side-effects, like before
  • We use [-1] to extract the last value, which acts like a return statement

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]) 

I cover a few ways to do this at http://chriswarbo.net/index.php?page=news&type=view&id=admin-s-blog%2Fanonymous-closures-in

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", with expressions, like "sys.stdout.write".

Note that I've added a "None" at the end, and extracted it using "[-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)" 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:

Of course, this is cumbersome but we can make helper functions:

callWith = lambda x, f: f(x) mkEnv = lambda _*: callWith({}, lambda d: (d.get, lambda k, v: (d.pop(k), d.setdefault(k, v)))) inEnv = lambda f: callWith(mkEnv(), f) stateful_lambda = inEnv(lambda get, set: (set('foo', 10), set('bar', get('foo') * get('foo')), set('foo', 2), get('foo') + get('bar'))[-1]) 

I cover a few ways to do this on my 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, with expressions, like sys.stdout.write.

Note that I've added a None at the end, and extracted it using [-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) 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 of stateful_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 state to a value {} without using an assignment statement (eg. state = {})
  • We treat keys and values in state as 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 of a = b and state.get(a) instead of a
  • We use a tuple to chain together our side-effects, like before
  • We use [-1] to extract the last value, which acts like a return statement

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 = delay(inEnv, lambda get, set: (set('foo', 10),   set('bar', get('foo') * get('foo')),   set('foo', 2),   get('foo') + get('bar'))[-1]) 
Source Link
Warbo
  • 1.2k
  • 8
  • 12

Hacking together a multi-statement lambda isn't quite as bad as pyrospade makes out: sure we could compose a bunch of monadic functions using bind, like in Haskell, but since we're in the impure world of Python, we might as well use side-effects to achieve the same thing.

I cover a few ways to do this at http://chriswarbo.net/index.php?page=news&type=view&id=admin-s-blog%2Fanonymous-closures-in

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", with expressions, like "sys.stdout.write".

Hence the following are equivalent:

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] 

Note that I've added a "None" at the end, and extracted it using "[-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)" return value, which we may or may not care about.

So we can sequence IO actions. What about local variables?

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:

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])({}) 

Of course, this is cumbersome but we can make helper functions:

callWith = lambda x, f: f(x) mkEnv = lambda _*: callWith({}, lambda d: (d.get, lambda k, v: (d.pop(k), d.setdefault(k, v)))) inEnv = lambda f: callWith(mkEnv(), f) stateful_lambda = inEnv(lambda get, set: (set('foo', 10), set('bar', get('foo') * get('foo')), set('foo', 2), get('foo') + get('bar'))[-1])