On 02/06/2014 05:14 AM, Nick Coghlan wrote:> On 6 February 2014 20:34, Ron
Adam
As far as what you're proposing goes, is it essentially a way to declare a function like (spelling out the lambdas fully):
def S(x): def _second(y): def _third(z): return x(z)(y(z)) return _third return _second
As something much shorter like this:
def S (x)(y)(z): return x(z)(y(z))
The main potential benefit I could see to a construct like that is that it may allow the more consistent creation of closures that support pickling, since the outer functions are guaranteed not to have any side effects and to have argument capture as their*only* significant state. This means that you could take the inner function, pickle it along with its closure variables and reconstruct that at the far end, only relying on the name of the outer function.
Such a construct could also make decorator factories easier to write.
def decorator(f): # Do something with f
def decorator_factory(some, args, here)(f): # Do something with f, but have access to the bound args.
There's an argument to be made that the extra parens in the function header are too easy to miss, but I still see "make it easier to write side-effect free closures" as an idea worth discussing further.
It's a different feature than the one I was suggesting, which was a type of continuations... but the two concepts are related and compatible. I think the S(x)(y)(z): is adding more complexity to function signatures which are already more complex than I like, but there is an alternate option... What if these *calls* were equivalent... S(x, y, z) == S(x)(y)(z) * There might need to be some additional device or syntax needed to make it work. (* see '@' comments further down.) That is on the call side, so the definition would still be the same. def(x, y, z): ... Which is nice, because it gives us partials at a lower level which may have some advantages over a library function. And it gives us a better way to define decorators. def deco(func, *args, **kwds): ... deco(func, *args, **kwds) == deco(func)(*args, **kwds) or deco(func)(*args)(**kwds) In effect making a decorators into partials. @deco def foo(...): ... Hmmm... Could the @ syntax be generalised in this case? @foo(func) Partial waiting for rest... Then the more general case... a_op = @foo(op) # Partial waiting for rest. a_x = @a_op(x) a = a_x(y) That would be cool, unifies decorators and partials with decorator syntax! I think it's even back-words compatible if you allow this equivalency. def foo(x, y): ... @foo(x, y) == foo(x, y) # An identity partial And the decorator case becomes... @partial def func(): ... And we get this equivalency as well.... @partial == partial # I think this could work. The reason these things are interesting to me is that, I've been thinking about the order of function arguments and if there could be some generalised concepts that can be applied to that problem. Being able to use normal functions effectively in these ways, (with partials, and continuations), is related to the order of the arguments and how they are commonly used. Cheers, Ron
On Sat, Feb 08, 2014 at 11:55:56AM -0600, Ron Adam wrote:
What if these *calls* were equivalent...
S(x, y, z) == S(x)(y)(z)
If you want Haskell, you know where to find it :-) http://www.haskell.org/haskellwiki/Currying But seriously, I think that is a perfectly fine design decision for a functional language like Haskell, but it's one which would be awful for a multi-paradigm language like Python which is aimed at a more general programming audience. That means you would never again get an explicit TypeError from leaving out a mandatory argument[1], since *no* arguments are mandatory, you'd just get a mysterious partial application object. When a beginner, or Java programmer, writes: result = getattr(obj) # Oops, forgot the attribute name. # much later on... print(result + 1) I wouldn't like to be the one to explain the eventual error they get. Or worse, they do this: method = getattr(obj) # Oops, forgot the method name. result = method(some_string) which is likely to mysteriously succeed for some strings, and fail for others. I think that for Python, having to explicitly perform partial application is a good thing. [1] With the possible exception of not providing any arguments at all. -- Steven
On 02/08/2014 11:55 AM, Ron Adam wrote:
On 02/06/2014 05:14 AM, Nick Coghlan wrote:> On 6 February 2014 20:34, Ron Adam
wrote:
Hmmm... Could the @ syntax be generalised in this case?
@foo(func) Partial waiting for rest...
Then the more general case...
a_op = @foo(op) # Partial waiting for rest. a_x = @a_op(x) a = a_x(y)
That would be cool, unifies decorators and partials with decorator syntax!
I think it's even back-words compatible if you allow this equivalency.
def foo(x, y): ...
@foo(x, y) == foo(x, y) # An identity partial
And the decorator case becomes...
@partial def func(): ...
And we get this equivalency as well....
@partial == partial # I think this could work.
The reason these things are interesting to me is that, I've been thinking about the order of function arguments and if there could be some generalised concepts that can be applied to that problem. Being able to use normal functions effectively in these ways, (with partials, and continuations), is related to the order of the arguments and how they are commonly used.
Here's a rough python version... It currently gets tripped up on optional arguments, but it does work for non optional cases. I think a builtin, along with the new signatures capabilities could fix those cases. (and to address Stevens conserns.. It would be explicit and won't effect the beginner cases if you don't use the @... In this example I'm using P. And a builtin function could work just as well. Although I would like the @ syntax to just work without the function. It's just a first attempt, with probably a lot of issues to be handled still, but it does show it's doable. Cheers, Ron #------------------------------- # A builtin could do this much better. class P: def __init__(self, f, *args, **kwds): self.f = f self.args = args self.kwds = kwds def __call__(self, *args, **kwds): args = list(self.args) + list(args) kwds.update(self.kwds) try: return self.f(*args, **kwds) except TypeError: return self.__class__(self.f, *args, **kwds) # This the easy way to write decorators!!! :-) @P def deco(f, *args): print("Got: ", args) return f(*args) # And a partial function example with it. :-) @P @deco def foo(x, y, z): return x + y + z print(foo(1, 2, 3)) print(foo(1, 2)(3)) print(foo(1)(2)(3)) #------------------------- The output... ra:~ >python3 partial_deco.py Got: () Got: (1, 2, 3) 6 Got: (1, 2) Got: (1, 2, 3) 6 Got: (1,) Got: (1, 2) Got: (1, 2, 3) 6
On Sat, Feb 8, 2014 at 4:41 PM, Ron Adam
On 02/08/2014 11:55 AM, Ron Adam wrote:
On 02/06/2014 05:14 AM, Nick Coghlan wrote:> On 6 February 2014 20:34, Ron Adam
wrote: Hmmm... Could the @ syntax be generalised in this case?
@foo(func) Partial waiting for rest...
Then the more general case...
a_op = @foo(op) # Partial waiting for rest. a_x = @a_op(x) a = a_x(y)
That would be cool, unifies decorators and partials with decorator syntax!
I think it's even back-words compatible if you allow this equivalency.
def foo(x, y): ...
@foo(x, y) == foo(x, y) # An identity partial
And the decorator case becomes...
@partial def func(): ...
And we get this equivalency as well....
@partial == partial # I think this could work.
The reason these things are interesting to me is that, I've been thinking about the order of function arguments and if there could be some generalised concepts that can be applied to that problem. Being able to use normal functions effectively in these ways, (with partials, and continuations), is related to the order of the arguments and how they are commonly used.
Here's a rough python version... It currently gets tripped up on optional arguments, but it does work for non optional cases.
I think a builtin, along with the new signatures capabilities could fix those cases.
(and to address Stevens conserns.. It would be explicit and won't effect the beginner cases if you don't use the @...
In this example I'm using P. And a builtin function could work just as well. Although I would like the @ syntax to just work without the function.
It's just a first attempt, with probably a lot of issues to be handled still, but it does show it's doable.
Cheers, Ron
#-------------------------------
# A builtin could do this much better. class P: def __init__(self, f, *args, **kwds): self.f = f self.args = args self.kwds = kwds def __call__(self, *args, **kwds): args = list(self.args) + list(args) kwds.update(self.kwds) try: return self.f(*args, **kwds) except TypeError: return self.__class__(self.f, *args, **kwds)
# This the easy way to write decorators!!! :-) @P def deco(f, *args): print("Got: ", args) return f(*args)
# And a partial function example with it. :-) @P @deco def foo(x, y, z): return x + y + z
print(foo(1, 2, 3)) print(foo(1, 2)(3)) print(foo(1)(2)(3)) #-------------------------
The output...
ra:~ >python3 partial_deco.py Got: () Got: (1, 2, 3) 6 Got: (1, 2) Got: (1, 2, 3) 6 Got: (1,) Got: (1, 2) Got: (1, 2, 3)
6
I experimented with something similar over at https://github.com/sigmavirus24/curryer but never released it. It's fairly well tested but I'm not sure I'd use it for anything other than a toy.
On 02/08/2014 04:46 PM, Ian Cordasco wrote:
I experimented with something similar over at https://github.com/sigmavirus24/curryer but never released it. It's fairly well tested but I'm not sure I'd use it for anything other than a toy.
Thanks I take a look, probably later this week, and see what is different. Today I made a nicer version. This fits both uses as decorator helpers and as a way to capture arguments for nested functions.What it's does is factor out nesting used to capture the arguments. And has some useful properties to help it work in more situations than the functools partial function. It also expresses lambda expressions surprisingly well! ;-) @P def I(x): """ I := λx.x (Identity) """ return x @P def K(x, y): """ K := λx.λy.x """ return x @P def S(x, y, z): """ S := λx.λy.λz.x z (y z) """ return x(z)(y(z)) # Use "_" as the lambda symbol. _ = P # Allows _(S, K, K, Z) in place of S(K)(K)(Z) or S(K, K, Z) assert _(I, 'A'), 'A' assert _(K, 'A', 'B'), 'B' assert _(S, K, K, 'C'), 'C' # Yes, This really works! :-) When I get back from a short 3 day trip, I'm going to test how it works in the library by replacing occurrences of "\n@..." with "\n@P(...)" and see what breaks. Should be interesting. I expect it to get hung up on optional args and keywords in places. But possibly not, as they will be included at the same time the partial is complete in most, if not all cases. One thing that bother me is that, catching TypeError is way to general. It would be very nice if "wrong number of args" raised a subclass of TypeError so we can test for just that type of error. Cheers, Ron (Tests not included to keep it short.) ------------------------------------------------------ """ ### NOTE: NOT FULLY TESTED!!! Especially with keywords. A PARTIAL PROTOCOL: Takes partial number of args and applies them to a callable. Calls the callable when all it's functions are present, else returns an updated partial object. THE RULES: * A partial is equivalent to the sum of it's partials. P(f, a, b, c) == P(f)(a)(b)(c) * A complete partial is equivalent to the function f When all the parts are provided. P(f, a, b, c) == f(a, b, c) * If f takes no arguments. (same rule) P(f) == f() * And empty partial evaluates to a partial. P() == P * If f requires at least one (not provided) argument. P(f)() == P(f) P()(f) == P(f) ERROR CASES: P(1)() # P(1) --> 1, 1 is not a callable. (* probably more) CONTROLS: It's possible to add controls for both the number of args, and number of Partial calls. Author: Ronald Adam (ron3200@gmail.com) """ class CollectMore: """ Collect more args and keywords. """ def __init__(self, f, *args, **kwds): self.f = f self.args = list(args) self.kwds = kwds def __call__(self, *args, **kwds): args = self.args + list(args) self.kwds.update(kwds) return self.f(*args, **self.kwds) def P(*args, **kwds): """ P - Partial function """ if len(args) == len(kwds) == 0: return P if len(args) > 0: f = args[0] if callable(f): a = args[1:] try: # No required args needed. # or all required args present. return f(*a, **kwds) except TypeError: # Better way to do this? pass elif len(args) == 1 and len(kwds) == 0: return f return CollectMore(P, *args, **kwds) # The implementation above also makes the functions that # use partial decorators become partials too. To avoid # that you can use an N-partial (vs impartial) partial # decorator. @P def NP(n, f, *args, **kwds): """ NP - N-Partial function Example: # Pass only 3 arguments for foo. @NP(3) def foo(*args): return args """ print("NP", n, f, *args, **kwds) if len(args) == 0: raise TypeError # Ready for rest, Partial catches this. elif len(args) < n: raise ValueError("%s is partial to %s values" % (f.__name__, n)) elif len(args) > n: raise ValueError("%s is partial to %s values" % (f.__name__, n)) else: return f(*args, **kwds) # TODO: A partial depth control function.
That got de-indented.... so here it is again... :-/ """ ### NOTE: NOT FULLY TESTED!!! Especially with keywords. A PARTIAL PROTOCOL: Takes partial number of args and applies them to a callable. Calls the callable when all it's functions are present, else returns an updated partial object. THE RULES: * A partial is equivalent to the sum of it's partials. P(f, a, b, c) == P(f)(a)(b)(c) * A complete partial is equivalent to the function f When all the parts are provided. P(f, a, b, c) == f(a, b, c) * If f takes no arguments. (same rule) P(f) == f() * And empty partial evaluates to a partial. P() == P * If f requires at least one (not provided) argument. P(f)() == P(f) P()(f) == P(f) ERROR CASES: P(1)() # P(1) --> 1, 1 is not a callable. (* probably more) CONTROLS: It's possible to add controls for both the number of args, and number of Partial calls. Author: Ronald Adam (ron3200@gmail.com) """ class CollectMore: """ Collect more args and keywords. """ def __init__(self, f, *args, **kwds): self.f = f self.args = list(args) self.kwds = kwds def __call__(self, *args, **kwds): args = self.args + list(args) self.kwds.update(kwds) return self.f(*args, **self.kwds) def P(*args, **kwds): """ P - Partial function """ if len(args) == len(kwds) == 0: return P if len(args) > 0: f = args[0] if callable(f): a = args[1:] try: # No required args needed. # or all required args present. return f(*a, **kwds) except TypeError: # Better way to do this? pass elif len(args) == 1 and len(kwds) == 0: return f return CollectMore(P, *args, **kwds) # The implementation above also makes the functions that # use partial decorators become partials too. To avoid # that you can use an N-partial (vs impartial) partial # decorator. @P def NP(n, f, *args, **kwds): """ NP - N-Partial function Example: # Pass only 3 arguments for foo. @NP(3) def foo(*args): return args """ print("NP", n, f, *args, **kwds) if len(args) == 0: raise TypeError # Ready for rest, Partial catches this. elif len(args) < n: raise ValueError("%s is partial to %s values" % (f.__name__, n)) elif len(args) > n: raise ValueError("%s is partial to %s values" % (f.__name__, n)) else: return f(*args, **kwds)
participants (3)
-
Ian Cordasco
-
Ron Adam
-
Steven D'Aprano