Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #21788 > unrolled thread

Currying in Python

Started byKiuhnm <kiuhnm03.4t.yahoo.it>
First post2012-03-17 02:21 +0100
Last post2012-03-20 11:13 +0100
Articles 10 — 4 participants

Back to article view | Back to comp.lang.python


Contents

  Currying in Python Kiuhnm <kiuhnm03.4t.yahoo.it> - 2012-03-17 02:21 +0100
    Re: Currying in Python Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2012-03-17 01:46 +0000
      Re: Currying in Python Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2012-03-17 02:14 +0000
    Re: Currying in Python Kiuhnm <kiuhnm03.4t.yahoo.it> - 2012-03-19 00:49 +0100
    Re: Currying in Python Ian Kelly <ian.g.kelly@gmail.com> - 2012-03-19 17:20 -0600
      Re: Currying in Python Kiuhnm <kiuhnm03.4t.yahoo.it> - 2012-03-20 01:24 +0100
      Re: Currying in Python Kiuhnm <kiuhnm03.4t.yahoo.it> - 2012-03-20 02:13 +0100
      Re: Currying in Python Kiuhnm <kiuhnm03.4t.yahoo.it> - 2012-03-20 11:06 +0100
    Re: Currying in Python Arnaud Delobelle <arnodel@gmail.com> - 2012-03-20 07:11 +0000
      Re: Currying in Python Kiuhnm <kiuhnm03.4t.yahoo.it> - 2012-03-20 11:13 +0100

#21788 — Currying in Python

FromKiuhnm <kiuhnm03.4t.yahoo.it>
Date2012-03-17 02:21 +0100
SubjectCurrying in Python
Message-ID<4f63e724$0$1386$4fafbaef@reader1.news.tin.it>
Here we go.

--->
def genCur(f, unique = True, minArgs = -1):
    """ Generates a 'curried' version of a function. """
    def geng(curArgs, curKwargs):
        def g(*args, **kwargs):
            nonlocal f, curArgs, curKwargs, minArgs;    # our STATIC data

            if len(args) or len(kwargs):
                # Allocates data for the next 'g'. We don't want to modify our
                # static data.
                newArgs = curArgs[:];
                newKwargs = dict.copy(curKwargs);

                # Adds positional arguments.
                newArgs += args;

                # Adds/updates keyword arguments.
                if unique:
                    # We don't want repeated keyword arguments.
                    for k in kwargs.keys():
                        if k in newKwargs:
                            raise(Exception("Repeated kw arg while unique = True"));
                newKwargs.update(kwargs);

                # Checks whether it's time to evaluate f.
                if minArgs >= 0 and minArgs <= len(newArgs) + len(newKwargs):
                    return f(*newArgs, **newKwargs);    # f has enough args
                else:
                    return geng(newArgs, newKwargs);    # f needs some more args
            else:
                return f(*curArgs, **curKwargs);    # the caller forced the evaluation
        return g;
    return geng([], {});

def cur(f, minArgs = -1):
    return genCur(f, True, minArgs);

def curr(f, minArgs = -1):
    return genCur(f, False, minArgs);

# Simple Function.
def f(a, b, c, d, e, f, g = 100):
    print(a, b, c, d, e, f, g);

# NOTE: '<====' means "this line prints to the screen".

# Example 1.
c1 = cur(f)(1);
c2 = c1(2, d = 4);              # Note that c is still unbound
c3 = c2(3)(f = 6)(e = 5);       # now c = 3
c3();                           # () forces the evaluation              <====
c4 = c2(30)(f = 60)(e = 50);    # now c = 30
c4();                           # () forces the evaluation              <====

print("\n------\n");

# Example 2.
c1 = curr(f)(1, 2)(3, 4);           # curr = cur with possibly repeated
                                    # keyword args
c2 = c1(e = 5)(f = 6)(e = 10)();    # ops... we repeated 'e' because we <====
                                    # changed our mind about it!
                                    # again, () forces the evaluation

print("\n------\n");

# Example 3.
c1 = cur(f, 6);             # forces the evaluation after 6 arguments
c2 = c1(1, 2, 3);           # num args = 3
c3 = c2(4, f = 6);          # num args = 5
c4 = c3(5);                 # num args = 6 ==> evalution                <====
c5 = c3(5, g = -1);         # num args = 7 ==> evaluation               <====
                            # we can specify more than 6 arguments, but
                            # 6 are enough to force the evaluation

print("\n------\n");

# Example 4.
def printTree(func, level = -1):
    if level == -1:
        printTree(cur(func), level + 1);
    elif level == 6:
        func(g = '')();     # or just func('')()
    else:
        printTree(func(0), level + 1);
        printTree(func(1), level + 1);

printTree(f);

print("\n------\n");

def f2(*args):
    print(", ".join(["%3d"%(x) for x in args]));

def stress(f, n):
    if n: stress(f(n), n - 1)
    else: f();          # enough is enough

stress(cur(f2), 100);
<---

Kiuhnm

[toc] | [next] | [standalone]


#21791

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2012-03-17 01:46 +0000
Message-ID<4f63ed13$0$29981$c3e8da3$5496439d@news.astraweb.com>
In reply to#21788
On Sat, 17 Mar 2012 02:21:32 +0100, Kiuhnm wrote:

> Here we go.
[snip code]


Have you looked at functools.partial?


import functools
new_func = functools.partial(func, ham, spam=23)


(I am aware that, technically, currying and partial function application 
are not quite the same thing, but it seems to me on a superficial reading 
that your function performs partial function application rather than 
actually currying. But I haven't read it in enough detail to be sure.)



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#21795

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2012-03-17 02:14 +0000
Message-ID<4f63f39e$0$29981$c3e8da3$5496439d@news.astraweb.com>
In reply to#21791
On Sat, 17 Mar 2012 01:46:59 +0000, Steven D'Aprano wrote:

> On Sat, 17 Mar 2012 02:21:32 +0100, Kiuhnm wrote:
> 
>> Here we go.
> [snip code]
> 
> 
> Have you looked at functools.partial?
> 
> 
> import functools
> new_func = functools.partial(func, ham, spam=23)
> 
> 
> (I am aware that, technically, currying and partial function application
> are not quite the same thing, but it seems to me on a superficial
> reading that your function performs partial function application rather
> than actually currying. But I haven't read it in enough detail to be
> sure.)

Okay, that was an incredibly superficial reading, because on a second 
reading, I can see it's nothing like partial function application. Sorry 
for the noise.



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#21869

FromKiuhnm <kiuhnm03.4t.yahoo.it>
Date2012-03-19 00:49 +0100
Message-ID<4f6674aa$0$1385$4fafbaef@reader2.news.tin.it>
In reply to#21788
On 3/17/2012 2:21, Kiuhnm wrote:
> Here we go.

I wrote an article about my approach to currying:
http://mtomassoli.wordpress.com/2012/03/18/currying-in-python/

Beginners should be able to understand it as well. Experienced 
programmers will probably want to skip some sections.

Kiuhnm

[toc] | [prev] | [next] | [standalone]


#21909

FromIan Kelly <ian.g.kelly@gmail.com>
Date2012-03-19 17:20 -0600
Message-ID<mailman.818.1332199274.3037.python-list@python.org>
In reply to#21788
I hope you don't mind if I critique your code a bit!

On Fri, Mar 16, 2012 at 7:21 PM, Kiuhnm
<kiuhnm03.4t.yahoo.it@mail.python.org> wrote:
> Here we go.
>
> --->
> def genCur(f, unique = True, minArgs = -1):

It is customary in Python for unsupplied arguments with no default to
use the value None, not -1.  That's what it exists for.

>    """ Generates a 'curried' version of a function. """
>    def geng(curArgs, curKwargs):
>        def g(*args, **kwargs):
>            nonlocal f, curArgs, curKwargs, minArgs;    # our STATIC data
>
>            if len(args) or len(kwargs):

Collections evaluate as true if they are not empty, so this could just be:

            if args or kwargs:

>                # Allocates data for the next 'g'. We don't want to modify our
>                # static data.
>                newArgs = curArgs[:];
>                newKwargs = dict.copy(curKwargs);
>
>                # Adds positional arguments.
>                newArgs += args;
>
>                # Adds/updates keyword arguments.
>                if unique:
>                    # We don't want repeated keyword arguments.
>                    for k in kwargs.keys():
>                        if k in newKwargs:
>                            raise(Exception("Repeated kw arg while unique = True"));
>                newKwargs.update(kwargs);

Since you're writing this for Python 3 (as evidenced by the use of the
nonlocal keyword), you could take advantage here of the fact that
Python 3 dictionary views behave like sets.  Also, you should use a
more specific exception type:

                if unique and not kwargs.keys().isdisjoint(newKwargs):
                    raise ValueError("A repeated keyword argument was supplied")

>                # Checks whether it's time to evaluate f.
>                if minArgs >= 0 and minArgs <= len(newArgs) + len(newKwargs):

With minArgs defaulting to None, that would be:

                if minArgs is not None and minArgs <= len(newArgs) +
len(newKwargs):

>                    return f(*newArgs, **newKwargs);    # f has enough args
>                else:
>                    return geng(newArgs, newKwargs);    # f needs some more args
>            else:
>                return f(*curArgs, **curKwargs);    # the caller forced the evaluation
>        return g;
>    return geng([], {});
>
> def cur(f, minArgs = -1):
>    return genCur(f, True, minArgs);
>
> def curr(f, minArgs = -1):
>    return genCur(f, False, minArgs);

The names "cur" and "curr" are terrible.  Good names should describe
what the function does without being too onerous to type, and the
addition of the duplicate "r" is not an obvious mnemonic for
remembering that the first one prohibits duplicate keyword arguments
and the second one allows them.  Why not more descriptive names like
"curry" and "curry_unique"?

That's all I've got.  All in all, it's pretty decent for a Python newbie.

Cheers,
Ian

[toc] | [prev] | [next] | [standalone]


#21911

FromKiuhnm <kiuhnm03.4t.yahoo.it>
Date2012-03-20 01:24 +0100
Message-ID<4f67ce36$0$1377$4fafbaef@reader2.news.tin.it>
In reply to#21909
On 3/20/2012 0:20, Ian Kelly wrote:
> I hope you don't mind if I critique your code a bit!

Not at all. Thank you for your time.

> On Fri, Mar 16, 2012 at 7:21 PM, Kiuhnm
> <kiuhnm03.4t.yahoo.it@mail.python.org>  wrote:
>> Here we go.
>>
>> --->
>> def genCur(f, unique = True, minArgs = -1):
[...]

I'll update my code following your corrections, but will keep "curr" :)

Kiuhnm

[toc] | [prev] | [next] | [standalone]


#21912

FromKiuhnm <kiuhnm03.4t.yahoo.it>
Date2012-03-20 02:13 +0100
Message-ID<4f67d9d3$0$1386$4fafbaef@reader2.news.tin.it>
In reply to#21909
On 3/20/2012 0:20, Ian Kelly wrote:
> I hope you don't mind if I critique your code a bit!

And I hope you don't mind that I included your name in my code. Let me 
know if I should remove it.

Kiuhnm

[toc] | [prev] | [next] | [standalone]


#21921

FromKiuhnm <kiuhnm03.4t.yahoo.it>
Date2012-03-20 11:06 +0100
Message-ID<4f6856b2$0$1392$4fafbaef@reader1.news.tin.it>
In reply to#21909
On 3/20/2012 0:20, Ian Kelly wrote:
> Since you're writing this for Python 3 (as evidenced by the use of the
> nonlocal keyword), you could take advantage here of the fact that
> Python 3 dictionary views behave like sets.  Also, you should use a
> more specific exception type:

As a side note, "nonlocal" isn't needed in my code, in fact I removed it 
right after my first post.

Kiuhnm

[toc] | [prev] | [next] | [standalone]


#21919

FromArnaud Delobelle <arnodel@gmail.com>
Date2012-03-20 07:11 +0000
Message-ID<mailman.822.1332227523.3037.python-list@python.org>
In reply to#21788
On 19 March 2012 23:20, Ian Kelly <ian.g.kelly@gmail.com> wrote:
> I hope you don't mind if I critique your code a bit!
>
> On Fri, Mar 16, 2012 at 7:21 PM, Kiuhnm
> <kiuhnm03.4t.yahoo.it@mail.python.org> wrote:
>> Here we go.
>>
>> --->
>> def genCur(f, unique = True, minArgs = -1):
>
> It is customary in Python for unsupplied arguments with no default to
> use the value None, not -1.  That's what it exists for.
>
>>    """ Generates a 'curried' version of a function. """
>>    def geng(curArgs, curKwargs):
>>        def g(*args, **kwargs):
>>            nonlocal f, curArgs, curKwargs, minArgs;    # our STATIC data

I don't know if all the rest of the code is below, but this line above
would only be necessary if you want to rebind f, curArgs, minArgs.
You don't seem to do it, so I think this line is unnecessary.

Also, your naming of variables disagrees with PEP 8 :)

>>            if len(args) or len(kwargs):
>
> Collections evaluate as true if they are not empty, so this could just be:
>
>            if args or kwargs:
>
>>                # Allocates data for the next 'g'. We don't want to modify our
>>                # static data.
>>                newArgs = curArgs[:];

Semicolon to end a statement?

>>                newKwargs = dict.copy(curKwargs);
>>
>>                # Adds positional arguments.
>>                newArgs += args;
>>
>>                # Adds/updates keyword arguments.
>>                if unique:
>>                    # We don't want repeated keyword arguments.
>>                    for k in kwargs.keys():
>>                        if k in newKwargs:
>>                            raise(Exception("Repeated kw arg while unique = True"));
>>                newKwargs.update(kwargs);
>
> Since you're writing this for Python 3 (as evidenced by the use of the
> nonlocal keyword), you could take advantage here of the fact that
> Python 3 dictionary views behave like sets.  Also, you should use a
> more specific exception type:
>
>                if unique and not kwargs.keys().isdisjoint(newKwargs):
>                    raise ValueError("A repeated keyword argument was supplied")
>
>>                # Checks whether it's time to evaluate f.
>>                if minArgs >= 0 and minArgs <= len(newArgs) + len(newKwargs):
>
> With minArgs defaulting to None, that would be:
>
>                if minArgs is not None and minArgs <= len(newArgs) +
> len(newKwargs):
>
>>                    return f(*newArgs, **newKwargs);    # f has enough args
>>                else:
>>                    return geng(newArgs, newKwargs);    # f needs some more args
>>            else:
>>                return f(*curArgs, **curKwargs);    # the caller forced the evaluation
>>        return g;
>>    return geng([], {});
>>
>> def cur(f, minArgs = -1):
>>    return genCur(f, True, minArgs);
>>
>> def curr(f, minArgs = -1):
>>    return genCur(f, False, minArgs);
>
> The names "cur" and "curr" are terrible.  Good names should describe
> what the function does without being too onerous to type, and the
> addition of the duplicate "r" is not an obvious mnemonic for
> remembering that the first one prohibits duplicate keyword arguments
> and the second one allows them.  Why not more descriptive names like
> "curry" and "curry_unique"?
>
> That's all I've got.  All in all, it's pretty decent for a Python newbie.
>
> Cheers,
> Ian
> --
> http://mail.python.org/mailman/listinfo/python-list

[toc] | [prev] | [next] | [standalone]


#21922

FromKiuhnm <kiuhnm03.4t.yahoo.it>
Date2012-03-20 11:13 +0100
Message-ID<4f685843$0$1374$4fafbaef@reader1.news.tin.it>
In reply to#21919
On 3/20/2012 8:11, Arnaud Delobelle wrote:
> On 19 March 2012 23:20, Ian Kelly<ian.g.kelly@gmail.com>  wrote:
>> I hope you don't mind if I critique your code a bit!
>>
>> On Fri, Mar 16, 2012 at 7:21 PM, Kiuhnm
>> <kiuhnm03.4t.yahoo.it@mail.python.org>  wrote:
>>> Here we go.
>>>
>>> --->
>>> def genCur(f, unique = True, minArgs = -1):
>>
>> It is customary in Python for unsupplied arguments with no default to
>> use the value None, not -1.  That's what it exists for.
>>
>>>     """ Generates a 'curried' version of a function. """
>>>     def geng(curArgs, curKwargs):
>>>         def g(*args, **kwargs):
>>>             nonlocal f, curArgs, curKwargs, minArgs;    # our STATIC data
>
> I don't know if all the rest of the code is below, but this line above
> would only be necessary if you want to rebind f, curArgs, minArgs.
> You don't seem to do it, so I think this line is unnecessary.

What a coincidence. I was just telling that to Ian Kelly. I removed it 
from the code in my article a few days ago but forgot to update my post 
on this ng.

> Also, your naming of variables disagrees with PEP 8 :)
>
>>>             if len(args) or len(kwargs):
>>
>> Collections evaluate as true if they are not empty, so this could just be:
>>
>>             if args or kwargs:
>>
>>>                 # Allocates data for the next 'g'. We don't want to modify our
>>>                 # static data.
>>>                 newArgs = curArgs[:];
>
> Semicolon to end a statement?

As above. Too many years of C++.

Kiuhnm

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web