Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #72904 > unrolled thread
| Started by | jongiddy <jongiddy@gmail.com> |
|---|---|
| First post | 2014-06-06 23:45 -0700 |
| Last post | 2014-06-08 11:00 -0600 |
| Articles | 20 on this page of 37 — 8 participants |
Back to article view | Back to comp.lang.python
Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-06 23:45 -0700
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-07 13:20 -0600
Re: Uniform Function Call Syntax (UFCS) Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2014-06-08 13:27 +1200
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 01:26 -0700
Re: Uniform Function Call Syntax (UFCS) Paul Sokolovsky <pmiscml@gmail.com> - 2014-06-08 15:06 +0300
Re: Uniform Function Call Syntax (UFCS) Marko Rauhamaa <marko@pacujo.net> - 2014-06-08 18:56 +0300
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 10:38 -0600
Re: Uniform Function Call Syntax (UFCS) Paul Sokolovsky <pmiscml@gmail.com> - 2014-06-08 19:40 +0300
Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 04:33 +0000
Re: Uniform Function Call Syntax (UFCS) Marko Rauhamaa <marko@pacujo.net> - 2014-06-09 09:25 +0300
Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 09:09 +0000
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 19:13 +1000
Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 13:37 +0000
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-10 01:08 +1000
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 09:24 -0700
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 09:34 -0700
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 10:54 -0600
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 03:10 +1000
Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 03:20 +0000
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 13:44 +1000
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 23:38 -0700
Re: Uniform Function Call Syntax (UFCS) Roy Smith <roy@panix.com> - 2014-06-08 23:45 -0400
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 02:25 -0700
Re: Uniform Function Call Syntax (UFCS) Roy Smith <roy@panix.com> - 2014-06-08 10:59 -0400
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 08:39 -0700
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 02:48 +1000
Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 03:53 +0000
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 14:53 +1000
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-09 08:24 -0600
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-09 23:43 -0700
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 11:08 -0600
Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 03:13 +1000
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 11:24 -0600
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 13:35 -0700
Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 01:15 -0700
Re: Uniform Function Call Syntax (UFCS) Paul Sokolovsky <pmiscml@gmail.com> - 2014-06-08 14:52 +0300
Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 11:00 -0600
Page 1 of 2 [1] 2 Next page →
| From | jongiddy <jongiddy@gmail.com> |
|---|---|
| Date | 2014-06-06 23:45 -0700 |
| Subject | Uniform Function Call Syntax (UFCS) |
| Message-ID | <8b96ae27-20fa-4df9-807e-c806fed983c0@googlegroups.com> |
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.
[toc] | [next] | [standalone]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2014-06-07 13:20 -0600 |
| Message-ID | <mailman.10859.1402169265.18130.python-list@python.org> |
| In reply to | #72904 |
On Sat, Jun 7, 2014 at 12:45 AM, jongiddy <jongiddy@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)
[toc] | [prev] | [next] | [standalone]
| From | Gregory Ewing <greg.ewing@canterbury.ac.nz> |
|---|---|
| Date | 2014-06-08 13:27 +1200 |
| Message-ID | <bvhsgeF2on9U2@mid.individual.net> |
| In reply to | #72928 |
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
[toc] | [prev] | [next] | [standalone]
| From | jongiddy <jongiddy@gmail.com> |
|---|---|
| Date | 2014-06-08 01:26 -0700 |
| Message-ID | <38058e64-0113-457c-ae63-cc66e8b569cd@googlegroups.com> |
| In reply to | #72947 |
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.
[toc] | [prev] | [next] | [standalone]
| From | Paul Sokolovsky <pmiscml@gmail.com> |
|---|---|
| Date | 2014-06-08 15:06 +0300 |
| Message-ID | <mailman.10876.1402229177.18130.python-list@python.org> |
| In reply to | #72958 |
Hello, On Sun, 8 Jun 2014 01:26:04 -0700 (PDT) jongiddy <jongiddy@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:pmiscml@gmail.com
[toc] | [prev] | [next] | [standalone]
| From | Marko Rauhamaa <marko@pacujo.net> |
|---|---|
| Date | 2014-06-08 18:56 +0300 |
| Message-ID | <8738ff2y4g.fsf@elektro.pacujo.net> |
| In reply to | #72961 |
Paul Sokolovsky <pmiscml@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
[toc] | [prev] | [next] | [standalone]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2014-06-08 10:38 -0600 |
| Message-ID | <mailman.10880.1402245571.18130.python-list@python.org> |
| In reply to | #72967 |
On Sun, Jun 8, 2014 at 9:56 AM, Marko Rauhamaa <marko@pacujo.net> wrote:
> Paul Sokolovsky <pmiscml@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:
[toc] | [prev] | [next] | [standalone]
| From | Paul Sokolovsky <pmiscml@gmail.com> |
|---|---|
| Date | 2014-06-08 19:40 +0300 |
| Message-ID | <mailman.10881.1402245958.18130.python-list@python.org> |
| In reply to | #72967 |
Hello, On Sun, 08 Jun 2014 18:56:47 +0300 Marko Rauhamaa <marko@pacujo.net> wrote: > Paul Sokolovsky <pmiscml@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:pmiscml@gmail.com
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2014-06-09 04:33 +0000 |
| Message-ID | <53953912$0$29988$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #72967 |
On Sun, 08 Jun 2014 18:56:47 +0300, Marko Rauhamaa wrote: > Paul Sokolovsky <pmiscml@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/
[toc] | [prev] | [next] | [standalone]
| From | Marko Rauhamaa <marko@pacujo.net> |
|---|---|
| Date | 2014-06-09 09:25 +0300 |
| Message-ID | <874mzu1twi.fsf@elektro.pacujo.net> |
| In reply to | #73005 |
Steven D'Aprano <steve+comp.lang.python@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
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2014-06-09 09:09 +0000 |
| Message-ID | <539579d1$0$29988$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #73008 |
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/
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2014-06-09 19:13 +1000 |
| Message-ID | <mailman.10906.1402305229.18130.python-list@python.org> |
| In reply to | #73012 |
On Mon, Jun 9, 2014 at 7:09 PM, Steven D'Aprano <steve+comp.lang.python@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
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2014-06-09 13:37 +0000 |
| Message-ID | <5395b8b1$0$29988$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #73014 |
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.python@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/
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2014-06-10 01:08 +1000 |
| Message-ID | <mailman.10918.1402326494.18130.python-list@python.org> |
| In reply to | #73027 |
On Mon, Jun 9, 2014 at 11:37 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> 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')
Language? English. :) Your point is more about accent, and if you
listen to some of the accents around the English countryside, you'll
know there are quite a few of them (which is a plot point in My Fair
Lady). Personally, I think the Midlands accents are rather delightful.
I've spent a month at a time in Buxton (one of the highest-altitude
towns in England) several times, always a pleasant time. But anyway.
The story is set in Penzance, in Cornwall, and in the typical Cornish
accent, the two words are fairly much alike. That said, though, some
performers (either through sloppiness or for deliberate comic effect)
do play around with that joke; but it's not hard to establish accents
that make the joke work.
> No wonder Gilbert and Sullivan had difficulty seeking success after HMS
> Pinafore...
>
> http://www.youtube.com/watch?v=0Y27MfF-n_Y
Hmm, that's a good idea! I wonder whether we could use it... you know,
same as so many other people already have. (Starship Pinafore
productions are actually fairly common) Wonder what's in our
calendar... ooh look, later on this year!
http://gilbertandsullivan.org.au/index.php?option=com_content&view=article&id=670&Itemid=565
Anyone want to come and join us? I'll be up in the lighting box every night.
ChrisA
[toc] | [prev] | [next] | [standalone]
| From | jongiddy <jongiddy@gmail.com> |
|---|---|
| Date | 2014-06-08 09:24 -0700 |
| Message-ID | <12d31393-3598-4304-8ce9-ac847ac21c64@googlegroups.com> |
| In reply to | #72961 |
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.
[toc] | [prev] | [next] | [standalone]
| From | jongiddy <jongiddy@gmail.com> |
|---|---|
| Date | 2014-06-08 09:34 -0700 |
| Message-ID | <dadd63ca-fae2-4dbc-b426-8dae3d2807ae@googlegroups.com> |
| In reply to | #72969 |
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)
[toc] | [prev] | [next] | [standalone]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2014-06-08 10:54 -0600 |
| Message-ID | <mailman.10883.1402246524.18130.python-list@python.org> |
| In reply to | #72969 |
On Sun, Jun 8, 2014 at 10:24 AM, jongiddy <jongiddy@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.
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2014-06-09 03:10 +1000 |
| Message-ID | <mailman.10886.1402247406.18130.python-list@python.org> |
| In reply to | #72969 |
On Mon, Jun 9, 2014 at 2:24 AM, jongiddy <jongiddy@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
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2014-06-09 03:20 +0000 |
| Message-ID | <53952807$0$29988$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #72978 |
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/
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2014-06-09 13:44 +1000 |
| Message-ID | <mailman.10903.1402285465.18130.python-list@python.org> |
| In reply to | #73000 |
On Mon, Jun 9, 2014 at 1:20 PM, Steven D'Aprano
<steve+comp.lang.python@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
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | comp.lang.python
csiph-web