Anonymous - Mercerenies/alakazam GitHub Wiki

Anonymous Functions

Operators

The Alakazam placeholder objects can be used with the following operators. Aside from a few exceptions which are listed at the bottom, either side of the placeholder operator expression can be a constant without causing any issues.

Note that, by default, the placeholders _1 through _5 are provided, for accessing the first five positional arguments of an argument list. If more arguments are required, anonymous instances can be explicitly constructed using zz.arg(n), which refers to the nth positional argument (1-based) of an argument list, and zz.kwarg(name), which refers to the keyword argument with the specified name.

_1.property_name
_1[_2]
_1 + _2
_1 - _2
_1 * _2
_1 / _2
_1 // _2
_1 % _2
_1 ** _2
_1 << _2
_1 >> _2
_1 & _2
_1 ^ _2
_1 | _2
- _1
+ _2
_1 == _2
_1 != _2
_1 < _2
_1 <= _2
_1 > _2
_1 >= _2

Function Calls

Using the function call operator on placeholder objects requires a bit of a different approach. Simply using _1(1) will not produce a function which calls its argument as a function; it will produce the identity function and then call it with the argument 1, because placeholder objects are designed to be called using this syntax to begin with. If you need to use function call syntax in your anonymous lambda, you must wrap the function itself in a zz.bind call.

# This defines a function which calls its first argument, passing 0 as a parameter.
zz.bind(_1)(0)
# This defines a function which calls foo, passing it the lambda argument
# as a keyword parameter.
zz.bind(foo)(keyword = _1)

Other than wrapping the function in zz.bind, this syntax can be used in the same way as any other operator. In particular, the function name can be constant or an anonymous lambda, and the argument list can contain positional and keyword arguments, which can also be either constant values or anonymous objects themselves. So, for instance, zz.bind(_1)(_2 + _3, "r", os = _4, lang = "Python") is a valid anonymous object, although it is not recommended that you use zz.bind for anything nearly this complex.

There is an alternative, and often cleaner, syntax for zz.bind. Instead of calling the operator and calling the returned value, simply pass the partial application arguments to zz.bind. So the following are equivalent.

zz.bind(_1)(_2, arg = _3)
zz.bind(_1, _2, arg = _3)

Note that this shortcut cannot be used if no arguments are being applied, so the following are not equivalent.

zz.bind(_1) # Returns a binding object
zz.bind(_1)() # Returns a function which calls its first argument

Assignment and Deletion

Alakazam also provides techniques for construction lambdas based on assignment and deletion. This would normally be impossible in Python, as assignment and deletion are statements and thus cannot be used in a lambda expression. However, with Alakazam, it becomes possible using the special helper functions zz.set and zz.delete. These two functions are unique in that they expect their first argument to be of a specific form and will raise an exception if the argument does not match the expected syntax. The following are valid forms.

zz.set(a[idx], expr)
zz.set(a.name, expr)
zz.delete(a[idx])
zz.delete(a.name)

The two zz.set calls construct lambdas which will behave as though the right-hand-side expression was assigned to the left-hand-side object with the = operator, calling either __setitem__ or __setattr__ on the receiving object. The two zz.delete calls will produce lambdas which behave as though the object had del called on it, calling either __delitem__ or __delattr__ as appropriate. Note that the a in these forms must be an anonymous lambda instance, which means that if you wish to use a constant value in this slot, you must wrap it in a zz.var call.

# This produces a lambda which sets its argument's greeting to "Hello!".
zz.set(_1.greeting, "Hello!")
# This produces a lambda which deletes the first element of its argument.
zz.delete(_1[0])
# This produces a lambda which deletes the nth element of the list foo, where
# n is passed as an argument. Note the zz.var call to mark the constant value.
zz.delete(zz.var(foo)[_1])

Note that zz.assign is a synonym for zz.set. The former should not be necessary unless you for some reason are importing Alakazam unqualified and would like to use it in conjunction with the built-in type set.

Exceptions and Notes

  • The _1.property_name syntax only works if property_name is not a magic method name and will fail if the anonymous function is called with an object that lacks a __dict__.
  • The _1[_2] syntax will fail if the object being subscripted is not an anonymous object. So _1[some_expr] will work but some_expr[_1] will not. In this case, explicitly wrapping the expression with zz.var will solve the problem, as in zz.var(some_expr)[_1].
  • Due to limitations in Python's overloading capabilities, the chained comparison operator syntax cannot be used with these placeholder objects. So _1 < _2 will work but _1 < _2 < _3 will produce a function that behaves in an undefined manner.
  • For consistency across different versions of Python, Alakazam will always use Python 3 division semantics for its anonymous lambda syntax. That is, when using Alakazam in Python 2, it will behave as though from __future__ import division is in scope.

Remember, placeholder expressions are for very short lambdas. If your expression is complicated enough that you have to wonder "Will this work correctly?", it's probably complicated enough that you should be explicitly using Python's lambda keyword. For instance, _1 + _2 is better than lambda x, y: x + y, but (_1 + _2) % _3 > _1 should probably use an explicit lambda, or even a named function.