pytoolz / toolz

A functional standard library for Python.
http://toolz.readthedocs.org/
Other
4.71k stars 263 forks source link

A shortcut to create Lambdas? #461

Open KnorrFG opened 5 years ago

KnorrFG commented 5 years ago

Hey, I love toolz, but the one thin I've been missing is a possibility to create lambdas in a less verbose way. At some point I had installed a library (which I can't seem to find anymore) that created lambdas through the use of _. Eg _ == x was translated to: lambda x: x == 3 but it had it's limits as something like len(_) would not work.

I wrote a function that takes a string, replaces percent signs by arguments and evals it:

import re

def _f_single_arg(function):
    body = (function.replace('%%', '<!>')
                    .replace('%', '_0')
                    .replace('<!>', '%'))
    return eval('lambda _0: ' + body)

def f(function):
    """Translates the provided expression in string form into a lambda. 
    Any %<n> where <n> is a int will be used as nth
    argument (starting with 0). If only one argument is required,
    a % can be used. To escape a % sign use %%."""
    single_percent_match = re.search(r"%[^\d]|%$", function)
    if single_percent_match:
        return _f_single_arg(function)

    matches = re.findall(r"%(?P<n>\d+)", function)
    n_args = 0 if len(matches) == 0 else\
        max(int(match) for match in matches) + 1
    replace_tuples = [(f'%{n}', f'_{n}') for n in range(n_args)]
    new_body = function
    # Not very functional, but since this is python
    # I think it's pragmatic
    for tup in replace_tuples:
        new_body = new_body.replace(*tup)
    head = 'lambda ' + ', '.join(pluck(1, replace_tuples)) + ': ' \
        if n_args > 0 else 'lambda *_0: '
    return eval(head + new_body)

Trying it out:

>>> f("%1")(0, "Hey")
"Hey"

>>> f('%')('Hey')
"Hey"

>>> f('"Hey"')(0, 1, 2)
"Hey"

I found this would fit one of my favorite libraries perfectly, what do you think about this?

jstrong-tios commented 5 years ago

random internet passerby here. I've considered hacking the cpython source code to add a more ergonomic lambda syntax. It couldn't be too hard to substitute a different keyword, say, fn, but I'm partial personally to javascript arrow notation ((x) => x * 2). apologies for hijacking thread (it's a subject near to my heart), but any thoughts on feasibility of this would be welcome.

KnorrFG commented 5 years ago

random internet passerby here. I've considered hacking the cpython source code to add a more ergonomic lambda syntax. It couldn't be too hard to substitute a different keyword, say, fn, but I'm partial personally to javascript arrow notation ((x) => x * 2). apologies for hijacking thread (it's a subject near to my heart), but any thoughts on feasibility of this would be welcome.

I'd love that, both options, but you'd have to write a PEP for that, and that would probably be discussed for a year and then be rejected ^^

jstrong-tios commented 5 years ago

the official process has already been tried and, as you expected, failed. I had in mind more of a rogue, pirate distribution. Perhaps we can bring back eager map (et al), reduce in the global namespace, and other things Guido killed over the years :).

mentalisttraceur commented 2 years ago

@jstrong-tios If you ever get around to it, I wanted to comment that one of the best things about those Javascript arrow functions you brought up is that they make writing curried functions very ergonomic: let add = x => y => x + y. I hope whatever you do in your fork of Python is similarly nice for both regular and curried functions.

mentalisttraceur commented 2 years ago

@KnorrFG since you said you couldn't find it anymore: placeholder is the best library I know of that does what you're talking about (lambda shorthand with _).

mentalisttraceur commented 1 year ago

@KnorrFG oh, also, placeholder has a workaround for edge cases like len(_): from placeholder import F and then F(len).

So for example instead of last_index = len(_) - 1 you would do last_index = F(len) - 1.

It's debatable if this is too unclear to be better, but it's there as an option.