[Python-ideas] Partial operator (and 'third-party methods' and 'piping') [was Re: Function composition (was no subject)]

Andrew Barnert abarnert at yahoo.com
Mon May 11 20:25:24 CEST 2015


On Monday, May 11, 2015 9:15 AM, Gregory Salvan <apieum at gmail.com> wrote:


>I don't want to insist and I respect your point of view, I just want to give a simplified real life example to show that function composition can be less painful than another syntax.

OK, let's compare your example to a Pythonic implementation of the same thing.

import re

ruser = re.compile("^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*$")
rdomain = re.compile("^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$")
rstrict_user = re.compile("^[a-z0-9][a-z0-9_-]+(?:\.[a-z0-9_-]+)*$")


def is_email_address(addr):
    user, domain = addr.split('@', 1)
    return ruser.match(user) and rdomain.match(domain)

def is_strict_email_address(addr):
    user, domain = addr.split('@', 1)
    return rstrictuser.match(user) and rdomain.match(domain)


def is_org_address(addr):
    return is_email_address(addr) and addr.ends_with('.org')

(An even better solution, given that you're already using regexps, might be to just use a single regexp with named groups for the user or strict-user, full domain, and TLD… but I've left yours alone.)

Far from being more painful, the Pythonic version is easier to write, easier to read, easier to debug, shorter, and understandable to even a novice, without having to rewrite anything in your head. It also handles invalid input by returning failure values and/or raising appropriate exceptions rather than asserting and exiting. And it's almost certainly going to be significantly more efficient. And it works with any string-like type (that is, any type that has a .split method and works with re.match). And if you have to debug something, you will have, e.g., values named user and domain, rather than both being named value at different levels on the call stack.

If you really want to come up with a convincing example for your idea, I'd take an example out of Learn You a Haskell or another book or tutorial and translate that to Python with your library. I suspect it would still have some of the same problems, but this example wouldn't even really be good in Haskell, so it's just making it harder to see why anyone would want anything like it. And by offering this as the response to Guido's "You're never going to convince me," well, if he _was_ still reading this thread with an open mind, he probably isn't anymore (although, to be honest, he probably wasn't reading it anyway).

>import re
>
>from lawvere import curry # curry is an arrow without type checking, inherits composition, mutiple dispatch
>
>user_match = re.compile("^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*$").match
>domain_match = re.compile("^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$").match
>strict_user_match = re.compile("^[a-z0-9][a-z0-9_-]+(?:\.[a-z0-9_-]+)*$").match
>
>@curry>def is_string(value):
>    assert isinstance(value, str), '%s is not a string' %value
>    return value
>
>@curry
>def apply_until_char(func, char, value):
>    func(value[:value.index(char)])
>    return value
>
>@curry
>def apply_from_char(func, char, value):
>    func(value[value.index(char) + 1:])
>    return value
>
>@curry
>
>def has_char(char, value):
>    assert value.count(char) == 1
>    return value
>
>@curry
>def assert_ends_with(text, value):
>    assert value.endswith(text), '%s do not ends with %s' % (value, text)
>    return value
>
>@curry
>def assert_user(user):
>    assert user_match(user) is not None, '%s is not a valid user name' % value
>    return user
>
>@curry
>def assert_strict_user(user):
>    assert strict_user_match(user) is not None, '%s is not a valid strict user' % value
>    return user
>
>@curry
>def assert_domain(domain):
>    assert domain_match(domain) is not None, '%s is not a valid domain name' % value
>    return domain
>
># currying (be made with partial)
>
>has_user = apply_until_char(assert_user, '@')
>
>has_strict_user = apply_until_char(assert_strict_user, '@')
>
>has_domain = apply_from_char(assert_domain, '@')
>
>
># composition:
>
>is_email_address = is_string >> has_char('@') >> has_user >> has_domain
>
>is_strict_email_address = is_string >> has_char('@') >> has_strict_user >> has_domain
>
>
># we just want org adresses ?
>
>is_org_addess = is_email_address >> assert_ends_with('.org')
>
>
>
>
>I found a lot of interest in this syntax, mainly for testing purpose, readability and maintenability of code.
>
>No matters if I'm a fish out of python waters. :)
>
>
>
>
>
>
>
>
>2015-05-11 16:41 GMT+02:00 Guido van Rossum <guido at python.org>:
>
>As long as I'm "in charge" the chances of this (or anything like it) being accepted into Python are zero. I get a headache when I try to understand code that uses function composition, and I end up having to laboriously rewrite it using more traditional call notation before I move on to understanding what it actually does. Python is not Haskell, and perhaps more importantly, Python users are not like Haskel users. Either way, what may work out beautifully in Haskell will be like a fish out of water in Python.
>>
>>I understand that it's fun to try to sole this puzzle, but evolving Python is more than solving puzzles. Enjoy debating the puzzle, but in the end Python will survive without the solution.
>>
>>
>>
>>-- 
>>
>>--Guido van Rossum (python.org/~guido)
>>_______________________________________________
>>Python-ideas mailing list
>>Python-ideas at python.org
>>https://mail.python.org/mailman/listinfo/python-ideas
>>Code of Conduct: http://python.org/psf/codeofconduct/
>>
>
>
>_______________________________________________
>Python-ideas mailing list
>Python-ideas at python.org
>https://mail.python.org/mailman/listinfo/python-ideas
>Code of Conduct: http://python.org/psf/codeofconduct/
>
>


More information about the Python-ideas mailing list