>>> On Feb 9, 2020, at 12:20, Soni L. <fakedme...@gmail.com> wrote:
>> On 2020-02-09 4:46 p.m., Andrew Barnert wrote:
>>> On Feb 9, 2020, at 05:40, Soni L. <fakedme...@gmail.com> wrote:
>>>> I'd rather have arg decorators tbh. they feel more natural than hacking on 
>>>> the type annotations.
>> Annotations look a lot more natural to me here.
>> Maybe that’s because your only example (besides the useless original one) 
>> looks like an implicit cast a la C++ and zillions of other languages:
>>  void cheese(Spam spam) {
>>      // whatever
>>  }
>>  Eggs eggs(42);
>>  cheese(eggs);
> 
> That's C++.

Yes, it is.

> But consider this:
> 
> class Foo:
> def bar(self):
>   pass
> 
> x = Foo()
> y = x.bar

So what? Consider this:

    auto y = &Foo::bar;

Neither one is relevant here. I think you’re just being misled by the fact that 
the implementation has to deal with a few of the same issues; to the user, 
method lookup and constructor calls look and act nothing alike, in both 
languages.

> The traits thing is just as much of an implicit cast as Python methods are an 
> implicit cast.

No, there are two huge differences.

First, you call the trait as a constructor, passing it the object to get back 
the object-as-this-trait, explicitly. While you _can_ do that with methods 
(`types.MethodType(type(x).bar, x)`), you don’t; you just write `x.bar`, and 
the actual method constructor is buried under two levels of abstraction 
(oversimplifying slightly, Python turns that into 
`type(bar:=type(x).bar).__get__(bar, x)`, and then `FunctionType.__get__(self, 
x)` returns `MethodType(self, x)`).

Second, even on the rare occasions when you do call the method constructor 
directly, it still doesn’t look like a cast—it isn’t a constructor from one 
object, but from two.

> Which is to say, it isn't. Traits don't have a constructor - instead, calling 
> them with an object will, if the object allows it, create a wrapper object to 
> handle the trait method lookups and any necessary wrapping or unwrapping of 
> objects.

But that is a constructor, for that wrapper object. (I’m pretty sure all of the 
traits libraries on PyPI even implement it with either __new__ on the trait 
base class or __call__ on a metaclass, but that’s not important.) “Constructor” 
is a much looser term in Python than in C++, where we call things like factory 
functions and classmethods “constructors” too because, as far as the caller is 
concerned, they’re completely interchangeable with calling a type directly.

But if you don’t like that naming, fine. Forget about “cast” and “constructor”: 
You can use anything that makes sense as an annotation, and can be called with 
a single argument to give the intended parameter value. Which is exactly what 
you’re trying to do. That’s why the first syntax you suggested, `def 
f(spam(x)):`, is workable in the first place.

Libraries like (I assume; I haven’t tested it) typical, or the quick&dirty 
thing I wrote will do exactly what you want here. And that includes traits as 
you defined them and as they’re implemented in libraries like strait. And if 
you don’t like the name `@typic.al` or `@implicit_cast` there’s nothing 
stopping you from calling it `@resolve_trait` (and restricting it to only 
applying annotations that are traits, while you’re at it). So, why do we need 
new syntax?

> This is also the way to access conflicting names: MyTrait(MyClass()).x() is 
> defined in the @impl(MyTrait) (and does print("Hello,") then 
> print("World!")), whereas MyClass().x() is the inherent method in MyClass 
> (and only prints "World!"). Note also that the @impl gets the full unwrapped 
> object, and not a TraitWrapper - this is by design.
> 
> This has many parallels to how methods in python do the "self" thing,

It has more parallels with super than with methods, but it’s not really the 
same as either.

>>> I'd need to create wrapper functions and deal with mixed args and kwargs. 
>>> as far as I know there aren't libraries that do it for me, either.
>> You only need to create one decorator that creates wrapping functions. There 
>> is a library that deals with args and kwargs for you, and it comes with 
>> Python: inspect. There are also libraries in PyPI to help write decorators, 
>> to do cast-like stuff, etc. If there isn’t a library that puts it all 
>> together the way you want, then that probably means there isn’t nearly 
>> enough demand for this feature to even put it in the stdlib, much less add 
>> new syntax for it. But that also means you can write it yourself and put it 
>> on PyPI and proselytize its use, and maybe create that demand.
> Don't forget: kw-only arguments, positional-only arguments, arguments that 
> can be either, default values, etc.

I didn’t forget them. More to the point, the inspect module didn’t forget them, 
so you can just rely on it. The quick&dirty hack I posted was for Python 3.7, 
so it doesn’t support positional-only parameters because they don’t exist in 
3.7, but it will handle keyword-only parameters and positional-or-keyword 
parameters, and default values, and decorated functions and partials and all 
kinds of other things I probably wouldn’t think to deal with at first, because 
the inspect module already deals with them for me.

_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/ZSRGCW7U52476UCSZNT52ZWC66IOSN6N/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to