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