Re: Uniform Function Call Syntax (UFCS)
So, just to summarise the discussion: There was some very mild support for readable pipelines, either using UFCS or an alternative syntax, but the Pythonic way to make combinations of function and method applications readable is to assign to variables over multiple lines. Make the code read down, not across. The idea that a class method could override a function using UFCS didn't get much traction. From Zen of Python, explicit is better than implicit means no differences in behaviour, depending on context. The fact that x.y and x.__getattr__ may behave differently under UFCS is also a problem. Since hasattr testing and AttributeError catching are both commonly used now, this could cause real problems, so could probably not be changed until Python 4. Finally, Gilbert Sullivan are definitely due a revival. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Steven D'Aprano steve+comp.lang.pyt...@pearwood.info: On Sun, 08 Jun 2014 18:56:47 +0300, Marko Rauhamaa wrote: In fact, what's the point of having the duality? x y == x.__lt__(y) [...] Consider x + y. What happens? #1 First, Python checks whether y is an instance of a *subclass* of x. If so, y gets priority, otherwise x gets priority. #2 If y gets priority, y.__radd__(x) is called, if it exists. If it returns something other than NotImplemented, we are done. #3 However if y.__radd__ doesn't exist, or it returns NotImplemented, then Python continues as if x had priority. #3 If x has priority, then x.__add__(y) is called, if it exists. If it returns something other than NotImplemented, we are done. #4 However if it doesn't exist, or it returns NotImplemented, then y.__radd__(x) is called, provided it wasn't already tried in step #2. #5 Finally, if neither object has __add__ or __radd__, or both return NotImplemented, then Python raises TypeError. In a word, Python has predefined a handful of *generic functions/methods*, which are general and standard in GOOPS (Guile's object system): (define-method (+ (x string) (y string)) ...) (define-method (+ (x matrix) (y matrix)) ...) (define-method (+ (f fish) (b bicycle)) ...) (define-method (+ (a foo) (b bar) (c baz)) ...) URL: http://www.gnu.org/software/guile/manual/html_node/ Methods-and-Generic-Functions.html Marko -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Monday, 9 June 2014 04:44:22 UTC+1, Chris Angelico wrote: This could be solved, though, by having a completely different symbol that means the thing on my left is actually the first positional parameter in the function call on my right, such as in your example: plus(1, 2) | divide(2) This would be absolutely identical to: divide(plus(1, 2), 2) Maybe you could even make it so that: plus(1, 2) x=| divide(y=2) is equivalent to divide(x=plus(1, 2), y=2) for the sake of consistency, and to allow the pipeline to inject something someplace other than the first argument. I'm not sure whether it'd be as useful in practice, though. It would depend partly on the exact syntax used. Obviously the pipe itself can't be used as it already means bitwise or, and this needs to be really REALLY clear about what's going on. But a data-flow notation would be of value in theory, at least. Perhaps a pipeline symbol plus an insertion marker would work better in Python: plus(1, 2) ~ divide(x=^, y=2) f.readlines() ~ map(int, ^) ~ min(^, key=lambda n: n % 10).str() ~ base64.b64encode(^, b'?-') ~ print(^) Stdio.read_file(foo.jpg) ~ Image.JPEG_decode(^).autocrop().rotate(0.5).grey() ~ Image.PNG_encode(^) ~ Stdio.write_file(foo.png, ^) -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, 09 Jun 2014 09:25:33 +0300, Marko Rauhamaa wrote: In a word, Python has predefined a handful of *generic functions/methods*, That's nine words :-) -- Steven D'Aprano http://import-that.dreamwidth.org/ -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, Jun 9, 2014 at 7:09 PM, Steven D'Aprano steve+comp.lang.pyt...@pearwood.info wrote: On Mon, 09 Jun 2014 09:25:33 +0300, Marko Rauhamaa wrote: In a word, Python has predefined a handful of *generic functions/methods*, That's nine words :-) I'll explain in two words: We propose to marry your daughters. http://math.boisestate.edu/gas/pirates/web_op/pirates13d.html ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, 09 Jun 2014 19:13:40 +1000, Chris Angelico wrote: On Mon, Jun 9, 2014 at 7:09 PM, Steven D'Aprano steve+comp.lang.pyt...@pearwood.info wrote: On Mon, 09 Jun 2014 09:25:33 +0300, Marko Rauhamaa wrote: In a word, Python has predefined a handful of *generic functions/methods*, That's nine words :-) I'll explain in two words: We propose to marry your daughters. http://math.boisestate.edu/gas/pirates/web_op/pirates13d.html In what language does often (\ˈȯ-fən, ÷ˈȯf-tən\) sound like orphan (\ˈȯr-fən\)? ('oh-fan', 'ov-ten' or even 'off-ten' versus 'or-fen') No wonder Gilbert and Sullivan had difficulty seeking success after HMS Pinafore... http://www.youtube.com/watch?v=0Y27MfF-n_Y -- Steven D'Aprano http://import-that.dreamwidth.org/ -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Jun 8, 2014 9:56 PM, Steven D'Aprano which means that hasattr (which is defined by attempting to get the attribute and seeing if an exception is thrown) has to return True. Yes. And this is a problem why? Earlier in this thread I pointed out that returning True creates problems for duck typing. But I'm now convinced that's preferable to making getattr and hasattr inconsistent. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Thanks for the extensive feedback. Here's my thoughts on how to address these issues. On Saturday, 7 June 2014 20:20:48 UTC+1, Ian wrote: It's a nice feature in a statically typed language, but I'm not sure how well it would work in a language as dynamic as Python. There are some questions that would need to be addressed. 1) Where should the function (or perhaps callable) be looked for? The most obvious place is the global scope. I think it would be a bit too far-reaching and inconsistent with other language features to reach directly inside imported modules (not to mention that it could easily get to be far too slow in a module with lots of imports). As a result it would have to be imported using the from module import function syntax, rather than the somewhat cleaner import module syntax. While there's nothing wrong with such imports, I'm not sure I like the thought of the language encouraging them any more than necessary. It would only work on functions in scope. x.len() would only work if len(x) would work. I actually think this would work better in Python than in D. In D, import module; imports all the symbols from the module, so it is easier to invoke a function unexpectedly. In Python, import module does not fill the namespace with lots of callable symbols, so UFCS would generally work with built-ins, local functions, or functions explicitly imported with from module import In this case, the need to use the from module import fname form can document that something unusual is happening. 2) What about getattr and hasattr? If I call hasattr(x, some_method), and x has no such attribute, but there is a function in the global scope named some_method, should it return True? If we instead have hasattr return False though, and have getattr raise an exception, then we have this very magical and confusing circumstance where getattr(x, 'method') raises an exception but x.method does not. So I don't think that's really a good scenario either. AS you suggest, the preferable route is that hasattr should return False. The object clearly does not have that attribute. It is a property of the current module that the object can use instance.fname. While the behaviour that hasattr(fname) returns False, but instance.fname works is an exception, and a function could be added to test this quickly, so new code that cares could use: if hasattr(instance, fname) or inscopecallable('fname'): The bigger problem I find is reading other code that uses UFCS and not realising that a method is not actually a method of the class, but requires importing a module. That can cause confusion when trying to use it in your own code. However, the need to use from module import fname would at least link the method name and the module. Also the idea makes me nervous in the thought that an incorrect attribute access could accidentally and somewhat randomly pick up some object from the environment. As before, I think the limited number of strange callable objects in most modules in Python protects against this. Of course, from module import * might cause problems, but that is already true. You need to be extra careful doing this, and should only do it for modules when you have a reasonable understanding of their exported names. But if you want to experiment with the idea, here's a (lightly tested) mixin that implements the behavior: Thanks for the headstart! I'll need to read up on descriptors to understand that last bit fully (when a function has a __get__ method). One problem with your untested code, the superclasses would need to be checked before using UFCS, so the structure is: try: return super().__getattr__(attr) except AttributeError: # resolve using UFCS -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 02:27:42 UTC+1, Gregory Ewing wrote: Also it doesn't sit well with Python's one obvious way to do it guideline, because it means there are *two* equally obvious ways to call a function. This provides a way to do something new (add class-optimized implementations for existing general-purpose functions). It also adds significant readability improvements by putting function-call chains in order. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 02:27:42 UTC+1, Gregory Ewing wrote: Also it doesn't sit well with Python's one obvious way to do it guideline, because it means there are *two* equally obvious ways to call a function. Actually, one of the best arguments against introducing UFCS is that Python currently provides two equivalent ways to check if an instance has an attribute: ask-permission using hasattr and ask-forgiveness using AttributeError. On the negative side, these currently equivalent (aside from performance) techniques could give different results using UFCS, potentially breaking some code. On the positive side, that means the proposal would add one two ways to do something and eliminate another two ways to do something, giving a net Zen of Python effect of zero. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Hello, On Sun, 8 Jun 2014 01:15:43 -0700 (PDT) jongiddy jongi...@gmail.com wrote: Thanks for the extensive feedback. Here's my thoughts on how to address these issues. On Saturday, 7 June 2014 20:20:48 UTC+1, Ian wrote: It's a nice feature in a statically typed language, but I'm not sure how well it would work in a language as dynamic as Python. There are some questions that would need to be addressed. 1) Where should the function (or perhaps callable) be looked for? The most obvious place is the global scope. I think it would be a bit too far-reaching and inconsistent with other language features to reach directly inside imported modules (not to mention that it could easily get to be far too slow in a module with lots of imports). As a result it would have to be imported using the from module import function syntax, rather than the somewhat cleaner import module syntax. While there's nothing wrong with such imports, I'm not sure I like the thought of the language encouraging them any more than necessary. It would only work on functions in scope. x.len() would only work if len(x) would work. In other words, you propose you add yet another check for each function call. But what many people has to say about Python is that it's slow. There should be lookout for how to make it faster, not yet slower. [] The bigger problem I find is reading other code that uses UFCS and not realising that a method is not actually a method of the class, but requires importing a module. That can cause confusion when trying to use it in your own code. Indeed, this UFCS idea adds inefficiency and confusion, but doesn't appear to solve any reasonable problem or add any firm benefit. -- Best regards, Paul mailto:pmis...@gmail.com -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Hello, On Sun, 8 Jun 2014 01:26:04 -0700 (PDT) jongiddy jongi...@gmail.com wrote: On Sunday, 8 June 2014 02:27:42 UTC+1, Gregory Ewing wrote: Also it doesn't sit well with Python's one obvious way to do it guideline, because it means there are *two* equally obvious ways to call a function. This provides a way to do something new (add class-optimized implementations for existing general-purpose functions). Python already has that - like, len(x) calls x.__len__() if it's defined (for objects where it makes sense for it to be defined). Many builtin functions have such behavior. For your custom functions, you can add similar conventions and functionality very easily (if you'll want to apply it to not your types, you'll need to subclass them, as expected). Getting x.foo() to call foo(x) is what's bigger problem, which has serious performance and scoping confusion implications, as discussed in other mails. It also adds significant readability improvements by putting function-call chains in order. Not sure what exactly you mean, but the order is usually pretty obvious - Python follows mathematical notation for function calls, and OO standard notation for method calls, one known from primary school, another from secondary (hopefully). They can be reordered with parentheses, which is also well-known basic math technique. -- Best regards, Paul mailto:pmis...@gmail.com -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
In article 1dd863ba-09e5-439b-8669-db65f3e99...@googlegroups.com, jongiddy jongi...@gmail.com wrote: On Sunday, 8 June 2014 02:27:42 UTC+1, Gregory Ewing wrote: Also it doesn't sit well with Python's one obvious way to do it guideline, because it means there are *two* equally obvious ways to call a function. Actually, one of the best arguments against introducing UFCS is that Python currently provides two equivalent ways to check if an instance has an attribute: ask-permission using hasattr and ask-forgiveness using AttributeError. On the negative side, these currently equivalent (aside from performance) techniques could give different results using UFCS, potentially breaking some code. Why? I assume a language which promoted the global namespace to be in the attribute search path (which, as far as I can tell, is what we're talking about here) would implement hasattr and raising AttributeError in a consistent way. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 15:59:14 UTC+1, Roy Smith wrote: Why? I assume a language which promoted the global namespace to be in the attribute search path (which, as far as I can tell, is what we're talking about here) would implement hasattr and raising AttributeError in a consistent way. It's slightly different. Although I used len() as an example, the idea is to allow any function to be used in this way, including local symbols. e.g. I could define: def squared(x): return x * x i = 3 i.squared() = 9 j = AClassThatImplements__mul__() j.squared() = whatever j * j returns but also: class AnotherClass: def __mul__(self, other): ... def squared(self): return specialised_method_for_calculating_squares() k = AnotherClass() k.squared() = calls method, not function In this case, there is a problem with letting hasattr('squared') return True for these first two instances. See Ian's post for a description of the problem. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Paul Sokolovsky pmis...@gmail.com: Python already has that - like, len(x) calls x.__len__() if it's defined In fact, what's the point of having the duality? len(x) == x.__len__() x y == x.__lt__(y) str(x) == x.__str__() etc. I suppose the principal reason is that people don't like UFCS. Plus some legacy from Python1 days. Lisp co. rigorously follow its UFCS. I think it works great, but that is what people most ridicule Lisp for. What do you think? Would you rather write/read: if size + len(data) = limit: or UFCS-ly: if size.__add__(data.__len__()).__le__(limit): Marko -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 13:06:08 UTC+1, Paul Sokolovsky wrote: Getting x.foo() to call foo(x) is what's bigger problem, which has serious performance and scoping confusion implications, as discussed in other mails. The performance hit will only occur when the attribute access is about to throw an AttributeError. Successful attribute accesses would be just as fast as before. And the cost of a symbol lookup is usually considered cheap compared to a thrown exception, so I don't believe there is a serious performance implication. As to the scoping confusion, I repeat that Python benefits from the fact that most modules will only have the builtins and local functions to worry about. This is a small enough space for users to manage. There's no surprises waiting to occur when the user adds or removes normal imports (a problem that can occur in D). It also adds significant readability improvements by putting function-call chains in order. Not sure what exactly you mean, but the order is usually pretty obvious - Python follows mathematical notation for function calls, and OO standard notation for method calls, one known from primary school, another from secondary (hopefully). They can be reordered with parentheses, which is also well-known basic math technique. A contrived example - which of these is easier to understand? from base64 import b64encode # works now print(b64encode(str(min(map(int, f.readlines()), key=lambda n: n % 10)), b'?-')) # would work with UFCS f.readlines().map(int).min(key=lambda n: n % 10).str().b64encode(b'?-').print() You can read the second form left to right, and arguments like b64encode's b'?-' are near the function call, making it a lot more obvious with which function this obscure argument is used. Note, I'm not suggesting either of these examples is good programming, but the same problem does occur in more reasonable scenarios - I just made this example a little extreme to emphasise the readability benefits. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 17:24:56 UTC+1, jongiddy wrote: # would work with UFCS f.readlines().map(int).min(key=lambda n: n % 10).str().b64encode(b'?-').print() Ooops - map is the wrong way round to support UFCS in this case. However, with UFCS, I could fix this by changing it to smap, and defining: def smap(seq, func): return map(func, seq) -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sun, Jun 8, 2014 at 9:56 AM, Marko Rauhamaa ma...@pacujo.net wrote: Paul Sokolovsky pmis...@gmail.com: Python already has that - like, len(x) calls x.__len__() if it's defined In fact, what's the point of having the duality? len(x) == x.__len__() x y == x.__lt__(y) str(x) == x.__str__() Python prefers having functions for operations that are common to a lot of types rather than methods. This allows for consistency of interface -- think of len() as the interface and .__len__() as the implementation. If .len() were the interface then it would be easy (and probably all too common) for Python programmers to change those interfaces in subclasses. It also means that if you want to pass the len function itself around, you just pass around len and know that it will work generally -- instead of passing around list.len and hoping that whatever it gets applied to is a list. This is a fair point against UFCS -- if x.len() comes to mean len(x) then it both makes it easy to change that interface (at least for the x.len() spelling) and makes it easier to pass around the function's implementation rather than its interface. What do you think? Would you rather write/read: if size + len(data) = limit: or UFCS-ly: if size.__add__(data.__len__()).__le__(limit): You may be misunderstanding the proposal. The UFCS style of that would be: if size + data.len() = limit: -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Hello, On Sun, 08 Jun 2014 18:56:47 +0300 Marko Rauhamaa ma...@pacujo.net wrote: Paul Sokolovsky pmis...@gmail.com: Python already has that - like, len(x) calls x.__len__() if it's defined In fact, what's the point of having the duality? len(x) == x.__len__() x y == x.__lt__(y) str(x) == x.__str__() etc. I suppose the principal reason is that people don't like UFCS. Plus some legacy from Python1 days. I personally don't see it as duality. There're few generic operators - the fact that they are really generic (apply to wide different classes of objects) is exactly the reason why the're defined in global namespace, and not methods. And yep, I see things like len as essentially an operator, even though its name consists of letters, and it has function call syntax. Then, there's just a way to overload these operators for user types, that's it. You *can* use x.__len__() but that's not how Python intends it. And like with any idea, one should not forget implementation side and efficiency - these operators are really core and expected to be used in performance-tight contexts, so they are implemented specially (optimized). Extending that handling to any function would cost either high memory usage, or high runtime cost. Lisp co. rigorously follow its UFCS. I think it works great, but that is what people most ridicule Lisp for. Exactly my thinking - there're bunch of languages which follow that UFCS-like idea, likely most homoiconic (or -like) do. Or you can use plain old C ;-). So, I don't see why people want to stuff this into Python - there're lot of ready alternatives. And Python provides very intuitive and obvious separation between generic functions and object methods IMHO, so there's nothing to fix. What do you think? Would you rather write/read: if size + len(data) = limit: How else could it be? or UFCS-ly: if size.__add__(data.__len__()).__le__(limit): OMG! Marko -- https://mail.python.org/mailman/listinfo/python-list -- Best regards, Paul mailto:pmis...@gmail.com -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, Jun 9, 2014 at 1:39 AM, jongiddy jongi...@gmail.com wrote: e.g. I could define: def squared(x): return x * x i = 3 i.squared() = 9 j = AClassThatImplements__mul__() j.squared() = whatever j * j returns but also: class AnotherClass: def __mul__(self, other): ... def squared(self): return specialised_method_for_calculating_squares() k = AnotherClass() k.squared() = calls method, not function In this case, there is a problem with letting hasattr('squared') return True for these first two instances. See Ian's post for a description of the problem. class Circle: def squared(self): raise NotImplementedError(Proven impossible in 1882) The trouble is that logically Circle does have a 'squared' attribute, while 3 doesn't; and yet Python guarantees this: foo.squared() # is equivalent [1] to func = foo.squared func() Which means that for (3).squared() to be 9, it has to be possible to evaluate (3).squared, which means that hasattr (which is defined by attempting to get the attribute and seeing if an exception is thrown) has to return True. Except that it's even more complicated than that, because hasattr wasn't defined in your module, so it has a different set of globals. In fact, this would mean that hasattr would become quite useless. (Hmm, PEP 463 might become a prerequisite of your proposal...) It also means that attribute lookup becomes extremely surprising any time the globals change; currently, x.y means exactly the same thing for any given object x and attribute y, no matter where you do it. The only way I can think of for all this to make sense is actually doing it the other way around. Instead of having x.y() fall back on y(x), have y(x) attempt x.y() first. To pull this off, you'd need a special bouncer around every global or builtin... which may be tricky. class MagicDict(dict): def __getitem__(self, item): # If this throws, let the exception propagate obj = super().__getitem__(item) if not callable(obj): return obj def bouncer(*a, **kw): if len(a)==1 and not kw: try: return getattr(a[0], item)() except AttributeError: pass return obj(*a, **kw) return bouncer import __main__ # Except that this bit doesn't work. __main__.__dict__ = MagicDict(__main__.__dict__) It's theoretically possible, along these lines, I think. Whether it's actually any good or not is another question, though! ChrisA [1] Modulo performance. CPython, AFAIK, does this exactly as written, but other Pythons may and do optimize the actual foo.squared() form to reduce heap usage. But in terms of visible effects, equivalent. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sun, Jun 8, 2014 at 10:24 AM, jongiddy jongi...@gmail.com wrote: A contrived example - which of these is easier to understand? from base64 import b64encode # works now print(b64encode(str(min(map(int, f.readlines()), key=lambda n: n % 10)), b'?-')) # would work with UFCS f.readlines().map(int).min(key=lambda n: n % 10).str().b64encode(b'?-').print() I prefer not making it a one-liner: data = map(int, f.readlines()) min_data = min(data, key=lambda n: n % 10) print(b64encode(str(smallest_data), b'?-')) Python's standard of having in-place methods return None also forces this to an extent. Whenever you want to tack on something like .append(), that's the end of your chain and it's time to start a new line anyway. Of course, you could always define something like: def appended(iterable, x): result = list(iterable) result.append(x) return result and use that in your chain. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sun, Jun 8, 2014 at 2:15 AM, jongiddy jongi...@gmail.com wrote: One problem with your untested code, the superclasses would need to be checked before using UFCS, so the structure is: try: return super().__getattr__(attr) except AttributeError: # resolve using UFCS And then if UFCS finds nothing, make sure the AttributeError gets reraised. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sun, Jun 8, 2014 at 10:48 AM, Chris Angelico ros...@gmail.com wrote: Except that it's even more complicated than that, because hasattr wasn't defined in your module, so it has a different set of globals. In fact, this would mean that hasattr would become quite useless. hasattr is a builtin, so it has no globals at all. It would have to use the calling scope for UFCS resolution as in my example implementation. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, Jun 9, 2014 at 2:24 AM, jongiddy jongi...@gmail.com wrote: A contrived example - which of these is easier to understand? from base64 import b64encode # works now print(b64encode(str(min(map(int, f.readlines()), key=lambda n: n % 10)), b'?-')) # would work with UFCS f.readlines().map(int).min(key=lambda n: n % 10).str().b64encode(b'?-').print() You can read the second form left to right Actually, this is something that I've run into sometimes. I can't think of any Python examples, partly because Python tends to avoid unnecessary method chaining, but the notion of data flow is a very clean one - look at shell piping, for instance. Only slightly contrived example: cat foo*.txt | gzip | ssh other_server 'gunzip | foo_analyze' The data flows from left to right, even though part of the data flow is on a different computer. A programming example might come from Pike's image library [1]. This definitely isn't what you'd normally call good code, but sometimes I'm working at the interactive prompt and I do something as a one-liner. It might look like this: Stdio.write_file(foo.png,Image.PNG.encode(Image.JPEG.decode(Stdio.read_file(foo.jpg)).autocrop().rotate(0.5).grey())); With UFCS, that could become perfect data flow: read_file(foo.jpg).JPEG_decode().autocrop().rotate(0.5).grey().PNG_encode().write_file(foo.png); I had to solve the syntactic ambiguity here by importing all the appropriate names, which does damage readability a bit. But you should be able to figure out what this is doing, with only minimal glancing at the docs (eg to find out that rotate(0.5) is rotating by half a degree). So the proposal does have some merit, in terms of final syntactic readability gain. The problem is the internal ambiguity along the way. ChrisA [1] http://pike.lysator.liu.se/generated/manual/modref/ex/predef_3A_3A/Image/Image.html -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, Jun 9, 2014 at 3:08 AM, Ian Kelly ian.g.ke...@gmail.com wrote: On Sun, Jun 8, 2014 at 10:48 AM, Chris Angelico ros...@gmail.com wrote: Except that it's even more complicated than that, because hasattr wasn't defined in your module, so it has a different set of globals. In fact, this would mean that hasattr would become quite useless. hasattr is a builtin, so it has no globals at all. It would have to use the calling scope for UFCS resolution as in my example implementation. Same difference. It can't simply look for the name in globals(), it has to figure out based on the caller's globals. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sun, Jun 8, 2014 at 11:13 AM, Chris Angelico ros...@gmail.com wrote: On Mon, Jun 9, 2014 at 3:08 AM, Ian Kelly ian.g.ke...@gmail.com wrote: On Sun, Jun 8, 2014 at 10:48 AM, Chris Angelico ros...@gmail.com wrote: Except that it's even more complicated than that, because hasattr wasn't defined in your module, so it has a different set of globals. In fact, this would mean that hasattr would become quite useless. hasattr is a builtin, so it has no globals at all. It would have to use the calling scope for UFCS resolution as in my example implementation. Same difference. It can't simply look for the name in globals(), it has to figure out based on the caller's globals. But that would all be done in getattr, so I don't think it affects hasattr's implementation at all. Since hasattr doesn't push anything onto the stack, getattr doesn't have to care whether it was called directly from Python or indirectly via getattr; either way the scope it needs is just the top frame of the stack. Could be a different matter in other implementations, though. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 18:24:28 UTC+1, Ian wrote: But that would all be done in getattr, so I don't think it affects hasattr's implementation at all. Since hasattr doesn't push anything onto the stack, getattr doesn't have to care whether it was called directly from Python or indirectly via getattr; either way the scope it needs is just the top frame of the stack. Could be a different matter in other implementations, though. In CPython, the UFCS would not be done in PyObject_GetAttr() as that would affect hasattr() as well. Instead, it would be implemented in the bytecode for LOAD_ATTR. If LOAD_ATTR was about to return an AttributeError, e.g. for [].len, it would perform the equivalent of a LOAD_NAME operation, with the difference that if the name is not found or is not callable, it returns AttributeError instead of NameError. If the name is found, then it would return something: for [].len, it would return the len() function wrapped to know that it's first argument was the list, which might be done by creating a fake Method object, as shown in Ian's code. But getattr([], 'len') and hasattr([], 'len') would both return False. I'm beginning to think it is too un-Pythonic - too much implicitness, unless it can be spelt differently, something like [].len(_) or [].len(...) to explicitly indicate that it plans to call a function, but might call a method if one is available. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, 09 Jun 2014 03:10:03 +1000, Chris Angelico wrote: [...] Actually, this is something that I've run into sometimes. I can't think of any Python examples, partly because Python tends to avoid unnecessary method chaining, but the notion of data flow is a very clean one - look at shell piping, for instance. Only slightly contrived example: cat foo*.txt | gzip | ssh other_server 'gunzip | foo_analyze' The data flows from left to right, even though part of the data flow is on a different computer. A programming example might come from Pike's image library [...] Stdio.write_file(foo.png,Image.PNG.encode(Image.JPEG.decode( Stdio.read_file(foo.jpg)).autocrop().rotate(0.5).grey())); With UFCS, that could become perfect data flow: read_file(foo.jpg).JPEG_decode().autocrop().rotate(0.5).grey() .PNG_encode().write_file(foo.png); As far as I am concerned, the biggest problem with chained method calls is that it encourages long one-liners. But I think chained calls are quite natural to read, and rather similar to the postfix notation used by Forth: foo.jpg read_file JPEG_decode autocrop 0.5 rotate grey PNG_encode foo.png write_file Although Forth has a (justified) reputation for being hard to read, postfix notation is not the cause. The above can be understood easily as a chain of function calls: read the file, then decode, then autocrop, then rotate, they grey, then encode, then write the file. You read and write the calls in the same first-to-last order as you would perform them. The equivalent prefix notation used by function calls is unnaturally backwards and painful to read: write_file(PNG_encode(grey(rotate(autocrop(JPEG_decode( read_file(foo.jpg))), 0.5))), foo.png); I had to solve the syntactic ambiguity here by importing all the appropriate names I'm not sure how this is *syntactic* ambiguity. As I see it, the only syntactic ambiguity occurs when you have functions of two arguments. Using shell notation: plus(1, 2) | divide(2) Assuming divide() takes two arguments, does that give 3/2 or 2/3? I would expect that the argument being piped in is assigned to the first argument. But I'm not sure how this sort of design ambiguity is fixed by importing names into the current namespace. (Note that Forth is brilliant here, as it exposes the argument stack and gives you a rich set of stack manipulation commands.) While we're talking about chaining method and function calls, I'll take the opportunity to link to this, in case anyone feels like adapting it to UFCS: http://code.activestate.com/recipes/578770 -- Steven D'Aprano http://import-that.dreamwidth.org/ -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, Jun 9, 2014 at 1:20 PM, Steven D'Aprano steve+comp.lang.pyt...@pearwood.info wrote: On Mon, 09 Jun 2014 03:10:03 +1000, Chris Angelico wrote: [...] Stdio.write_file(foo.png,Image.PNG.encode(Image.JPEG.decode( Stdio.read_file(foo.jpg)).autocrop().rotate(0.5).grey())); With UFCS, that could become perfect data flow: read_file(foo.jpg).JPEG_decode().autocrop().rotate(0.5).grey() .PNG_encode().write_file(foo.png); I had to solve the syntactic ambiguity here by importing all the appropriate names I'm not sure how this is *syntactic* ambiguity. The ambiguity I'm talking about here is with the dot. The original version has Stdio.read_file as the first function called; for a Python equivalent, imagine a string processing pipeline and having re.sub in the middle of it. You can't take re.sub as the name of an attribute on a string without some fiddling around that completely destroys the point of data-flow syntax. So I cheated, and turned everything into local (imported) names (adorning the ones that needed it). This is a bad idea in Pike for the same reason it's a bad idea in Python - you end up with a massively polluted global namespace. This could be solved, though, by having a completely different symbol that means the thing on my left is actually the first positional parameter in the function call on my right, such as in your example: plus(1, 2) | divide(2) This would be absolutely identical to: divide(plus(1, 2), 2) Maybe you could even make it so that: plus(1, 2) x=| divide(y=2) is equivalent to divide(x=plus(1, 2), y=2) for the sake of consistency, and to allow the pipeline to inject something someplace other than the first argument. I'm not sure whether it'd be as useful in practice, though. It would depend partly on the exact syntax used. Obviously the pipe itself can't be used as it already means bitwise or, and this needs to be really REALLY clear about what's going on. But a data-flow notation would be of value in theory, at least. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
In article 53952807$0$29988$c3e8da3$54964...@news.astraweb.com, Steven D'Aprano steve+comp.lang.pyt...@pearwood.info wrote: (Note that Forth is brilliant here, as it exposes the argument stack and gives you a rich set of stack manipulation commands.) As does PostScript (which, despite its reputation as a printer format, is really a full-fledged programming language). I suspect that people who didn't grow up with RPN (i.e. H/P calculators) find it amazingly obtuse. In much the same way I find Objective-C amazingly obtuse. Oh, wait, that's the other thread. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, 09 Jun 2014 02:48:13 +1000, Chris Angelico wrote: class Circle: def squared(self): raise NotImplementedError(Proven impossible in 1882) The trouble is that logically Circle does have a 'squared' attribute, while 3 doesn't; and yet Python guarantees this: foo.squared() # is equivalent [1] to func = foo.squared func() Which means that for (3).squared() to be 9, it has to be possible to evaluate (3).squared, Given UFCS, that ought to return the global squared function, curried with 3 as its first (and only) argument. UFCS would be a pretty big design change to Python, but I don't think it would be a *problem* as such. It just means that x.y, hasattr(x, y) etc. would mean something different to what they currently mean. which means that hasattr (which is defined by attempting to get the attribute and seeing if an exception is thrown) has to return True. Yes. And this is a problem why? Obviously it would mean that the semantics of hasattr will be different than they are now, but it's still a coherent set of semantics. In fact, one can already give a class a __getattr__ method which provides UFCS functionality. (Hmmm, you need a way to get the caller's globals. You know, this keeps coming up. I think it's high-time Python offered this as a supported function.) That's no more a problem than any other dynamically generated attribute. Stick that __getattr__ in object itself, and UFCS is now language wide. That would make an awesome hack for anyone wanting to experiment with this! Except that it's even more complicated than that, because hasattr wasn't defined in your module, so it has a different set of globals. hasattr doesn't care about globals, nor does it need to. hasattr behaves like the equivalent to: def hasattr(obj, name): try: obj.name except AttributeError: return False return True give or take. And yes, if accessing your attribute has side effects, using hasattr does too: py class Spam(object): ... @property ... def spam(self): ... print(Spam spam spam spam LOVERLY SPM) ... return spam ... py x = Spam() py hasattr(x, spam) Spam spam spam spam LOVERLY SPM True If that's a worry to you, you can try inspect.getattr_static. In fact, this would mean that hasattr would become quite useless. (Hmm, PEP 463 might become a prerequisite of your proposal...) It also means that attribute lookup becomes extremely surprising any time the globals change; currently, x.y means exactly the same thing for any given object x and attribute y, no matter where you do it. *cough* class Example: def __getattr__(self, name): if name == 'module_name': if __name__ == '__main__': return NOBODY expects the Spanish Inquisition! else: return __name__ raise AttributeError(no attribute %r % name) :-) -- Steven D'Aprano http://import-that.dreamwidth.org/ -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sun, 08 Jun 2014 18:56:47 +0300, Marko Rauhamaa wrote: Paul Sokolovsky pmis...@gmail.com: Python already has that - like, len(x) calls x.__len__() if it's defined In fact, what's the point of having the duality? len(x) == x.__len__() x y == x.__lt__(y) str(x) == x.__str__() Interface on the left, implementation on the right. That's especially obvious when you consider operators like + - * etc. Consider x + y. What happens? #1 First, Python checks whether y is an instance of a *subclass* of x. If so, y gets priority, otherwise x gets priority. #2 If y gets priority, y.__radd__(x) is called, if it exists. If it returns something other than NotImplemented, we are done. #3 However if y.__radd__ doesn't exist, or it returns NotImplemented, then Python continues as if x had priority. #3 If x has priority, then x.__add__(y) is called, if it exists. If it returns something other than NotImplemented, we are done. #4 However if it doesn't exist, or it returns NotImplemented, then y.__radd__(x) is called, provided it wasn't already tried in step #2. #5 Finally, if neither object has __add__ or __radd__, or both return NotImplemented, then Python raises TypeError. That's a lot of boilerplate if you were required to implement it yourself in every single operator method. Better, Python handles all the boiler plate, all you have to do is just handle the cases you care about, and return NotImplemented for everything else. -- Steven D'Aprano http://import-that.dreamwidth.org/ -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Mon, Jun 9, 2014 at 1:53 PM, Steven D'Aprano steve+comp.lang.pyt...@pearwood.info wrote: which means that hasattr (which is defined by attempting to get the attribute and seeing if an exception is thrown) has to return True. Yes. And this is a problem why? Obviously it would mean that the semantics of hasattr will be different than they are now, but it's still a coherent set of semantics. Coherent perhaps, but in direct opposition to the OP's statement about how hasattr should return False even if there's a global to be found. A coherent meaning for this kind of thing would almost certainly not be possible within the OP's requirements, although it's entirely possible something sensible could be put together. (By the way, would (3).squared return a curried reference to squared as of when you looked it up, or would it return something that late-binds to whatever 'squared' is in scope as of when you call it? If the latter, then hasattr would have to always return True, and getattr would have to return something that does the late-bind lookup and turns NameError into AttributeError.) ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Uniform Function Call Syntax (UFCS)
The language D has a feature called Uniform Function Call Syntax, which allows instance methods to be resolved using function calls. In Python terms, the call: x.len() would first check if 'x' has a method 'len', and would then look for a function 'len', passing 'x' as the first argument. The big wins are: - the ability to override functions with more optimal class-specific implementations. (Of course, len() is a bad example, since we already have a way to override it, but there are other functions that do not have a special method). - the readability of a.b().c().d() vs c(a.b()).d() Here's a few links discussing the feature in D: - First, a fairly gentle this is cool post: http://www.kr41.net/2013/08/27/uniform_function_call_syntax_in_d.html - Second, an article from the Walter Bright, the creator of D: http://www.drdobbs.com/cpp/uniform-function-call-syntax/232700394 Has this been discussed or proposed before? I found PEP's 443 and 3124, which provide a form of function overloading, but not reordering. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sat, Jun 7, 2014 at 12:45 AM, jongiddy jongi...@gmail.com wrote: The language D has a feature called Uniform Function Call Syntax, which allows instance methods to be resolved using function calls. In Python terms, the call: x.len() would first check if 'x' has a method 'len', and would then look for a function 'len', passing 'x' as the first argument. The big wins are: - the ability to override functions with more optimal class-specific implementations. (Of course, len() is a bad example, since we already have a way to override it, but there are other functions that do not have a special method). - the readability of a.b().c().d() vs c(a.b()).d() Here's a few links discussing the feature in D: - First, a fairly gentle this is cool post: http://www.kr41.net/2013/08/27/uniform_function_call_syntax_in_d.html - Second, an article from the Walter Bright, the creator of D: http://www.drdobbs.com/cpp/uniform-function-call-syntax/232700394 Has this been discussed or proposed before? I found PEP's 443 and 3124, which provide a form of function overloading, but not reordering. It's a nice feature in a statically typed language, but I'm not sure how well it would work in a language as dynamic as Python. There are some questions that would need to be addressed. 1) Where should the function (or perhaps callable) be looked for? The most obvious place is the global scope. I think it would be a bit too far-reaching and inconsistent with other language features to reach directly inside imported modules (not to mention that it could easily get to be far too slow in a module with lots of imports). As a result it would have to be imported using the from module import function syntax, rather than the somewhat cleaner import module syntax. While there's nothing wrong with such imports, I'm not sure I like the thought of the language encouraging them any more than necessary. Probably local (and by extension nonlocal) scoping is fine also. This makes perfect sense to me: def some_function(x): def my_local_extension_method(self): return 42 print(x.my_local_extension_method()) 2) What about getattr and hasattr? If I call hasattr(x, some_method), and x has no such attribute, but there is a function in the global scope named some_method, should it return True? I think the answer is no, because that could mess with duck typing. Say I have a function that checks the methods of some object that was passed in, and it then passes that object on to some other function: def gatekeeper_for_f(x): # f behaves badly if passed an x without a key_func, # so verify that it has one. if not hasattr(x, 'key_func'): raise TypeError(x has no key_func) else: return f(x) Okay, so suppose we pass in to gatekeeper_for_f a non-conformant object, but there happens to be a key_func in our global scope, so hasattr returns True. Great! gatekeeper_for_f can call x.key_func(). But that doesn't mean that *f* can call x.key_func(), if it happened to be defined in a different global scope. If we instead have hasattr return False though, and have getattr raise an exception, then we have this very magical and confusing circumstance where getattr(x, 'method') raises an exception but x.method does not. So I don't think that's really a good scenario either. Also the idea makes me nervous in the thought that an incorrect attribute access could accidentally and somewhat randomly pick up some object from the environment. In statically typed languages this isn't a huge concern, because the extension method has to take an appropriately typed object as its first argument (and in C# it even has to be explicitly marked as an extension method), so if you resolve an extension method by accident, at least it will be something that makes sense as a method. Without the static typing you could mistakenly pick up arbitrary functions that have nothing at all to do with your object. But if you want to experiment with the idea, here's a (lightly tested) mixin that implements the behavior: import inspect import types class ExtensionMethodMixin: def __getattr__(self, attr): parent_frame = inspect.currentframe().f_back if parent_frame: try: func = parent_frame.f_locals[attr] except KeyError: func = parent_frame.f_globals.get(attr) if callable(func): try: __get__ = func.__get__ except AttributeError: return types.MethodType(func, self) else: return __get__(self, type(self)) return super().__getattr__(attr) -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
Ian Kelly wrote: It's a nice feature in a statically typed language, but I'm not sure how well it would work in a language as dynamic as Python. Also it doesn't sit well with Python's one obvious way to do it guideline, because it means there are *two* equally obvious ways to call a function. -- Greg -- https://mail.python.org/mailman/listinfo/python-list