
On 2020-02-09 4:46 p.m., Andrew Barnert wrote:
On Feb 9, 2020, at 05:40, Soni L. <fakedme+py@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);
Oversimplifying the C++ rules a bit, this will call the Spam::Spam(Eggs) constructor on eggs to get a Spam, and that will be the value of spam. Which is exactly what you’re trying to do, so spelling it similarly seems reasonable:
@implicit_cast def cheese(spam: Spam): # whatever
eggs = Eggs(42) cheese(eggs)
This would of course raise a TypeError at runtime instead of at compile time if there is no way to construct a Spam from an Eggs, and it requires a decorator because it’s not built deep into the language as it is in C++, but otherwise it’s the same idea and the most obvious way to translate its spelling.
Maybe if you had a good example that wasn’t an implicit type cast, this similarity would be an argument against using annotations rather than an argument for using them? But it’s hard to know without seeing such an example, or even knowing if you have such an example.
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's C++. But consider this: class Foo: def bar(self): pass x = Foo() y = x.bar The traits thing is just as much of an implicit cast as Python methods are an implicit cast. 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. 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, altho I can understand the confusion if you're not familiar with traits in e.g. Rust. that can be either, default values, etc.