5 ms·
I think this isn't quite what I want, because implicit structural typing has a single shared namespace of methods. If the Ducky protocol says "implementers shou
by neolog 6y ago
I think this isn't quite what I want, because implicit structural typing has a single shared namespace of methods. If the Ducky protocol says "implementers should have .quack()" and I define FringeScientist().quack(), Mypy will think it's Ducky but it's just a naming coincidence. I want to say which protocols I'm implementing. Mypy can't tell whether my Foo.quack() is supposed to be an implementation of FringeScientist.quack() or Ducky.quack().
- morelisp 6y agoIn practice this happens an order of magnitude less than "I want to handle two things that match the same protocol that I didn't write and can't extend" and "I need to pass something that would work fine to a poorly-typed library" combined. That's before we even get into the philosophical issue of, who are you, the lowly library implementor, to say those aren't something appropriate to abstract over in my application?
- erik_seaberg 6y agoHow can two objects present exactly the same API without either one knowingly emulating the other? Examples always seem to use one method taking no args, which don’t seem realistic.
- XorNot 6y agoWhat's missing in this example is stub typing of the output values. Scientist.quack really shouldn't be returning plain strings, and neither should ducky.quack if the data in them is contextually different. Things should only become basic types at the last possible moment.
- neolog 6y agoWhich language ecosystems operate like that in practice?
- morelisp 6y agoGo has no subtyping and only supports polymorphism via structural types (and some weak support for untyped constant values). It also has very convenient syntax to define newtypes from primitives (or non-primitives, but this is less commonly used).
- morelisp 6y ago> Examples always seem to use one method taking no args, which don’t seem realistic. This is one of the reasons it is so rare to actually have confusable types in a real program.
- nemothekid 6y agoOne thing I did come across once before is when two APIs do something similar such as parsing and provide the same name. A couple of years ago I remember I was working with an API in Go that the function Scan which was `fn(interface{}) -> Error`, which was used to decode SQL values. I pulled in another library which provided that function but in a separate context - text parsing. The interfaces matched, the compiler accepted it, but the application blew up at runtime.
- SatvikBeri 6y agoA common example I ran into is machine learning methods and data transformations such as scaling have have `fit` and `transform` methods, and I'd like to write code that can take multiple such objects and compose them in a pipeline, along with some custom logic. Structural typing makes that easy to typecheck, nominal typing makes it difficult because those two classes don't have a parent class that contains those methods. Extending this, if the class has a `partial_fit` method, sometimes I want to use that, and fallback to `fit` when it doesn't.
- neolog 6y agoNominal typing just means someone has to declare that LinearClassifier implements Classifier. That could be done by the LinearClassifier maintainer or by the user.
- morelisp 6y agoWhat languages let the user do this?
- neolog 6y agoPython's zope.interface can do it. Julia's still figuring out their trait system, but some implementations allow that. I don't know Clojure but I wouldn't be surprised to hear if their Spec allows it.
- morelisp 6y agoI don't think either zope.interface or spec is really thought of as a nominal typing system? Certainly not spec. zope.interface of course can late-bind classes to interfaces but only because it's actually a runtime thing and Python late-binds everything.
- kuschku 6y agoBefore Android supported Optional, there were dozens of independent reimplementations of the API. All exactly identical, but all in different namespaces and therefore not compatible. And the official one obviously couldn't be used.
- lmm 6y agoConsider an immutable list and a read-only view of a mutable list (that another thread may mutate concurrently). They offer exactly the same API, but it's a major mistake to pass one where the other was expected.
- coolreader18 6y agoWell, that's duck typing - looks like a duck, quacks like a duck, it's a Duck[y]. And while it's obviously hard to extrapolate simple examples like `duck_war` to real-world use-cases, I think signatures being different between methods of the same name on different protocols is generally enough to differentiate between them.
- orblivion 6y agoDon't Golang interfaces work this way as well? (Not that I'm a fan).
- l0b0 6y agoThis is only a problem if the `FringeScientist.quack` signature is identical to Ducky.quack: $ cat test.py from dataclasses import dataclass from typing import Protocol class Ducky(Protocol): def quack(self) -> None: "Quack." class FringeScientist: def quack(self) -> bool: return True @dataclass class Duck: quiet: bool = False def quack(self) -> None: print("Quack." if self.quiet else "QUACK!") def duck_war(aggressor: Ducky, defender: Ducky) -> None: aggressor.quack() defender.quack() print("The only winning move is not to play.") duck = Duck() asshat = FringeScientist() duck_war(duck, asshat) $ mypy test.py test.py:32: error: Argument 2 to "duck_war" has incompatible type "FringeScientist"; expected "Ducky" [arg-type] test.py:32: note: Following member(s) of "FringeScientist" have conflicts: test.py:32: note: Expected: test.py:32: note: def quack(self) -> None test.py:32: note: Got: test.py:32: note: def quack(self) -> bool
- brundolf 6y agoThis is why Rust makes you explicitly implement a trait for a given type, even if the type doesn't require any new methods to be implemented
- c6401 6y agoThat's the point of protocols. It doesn't matter if it's a duck or not, what matters is that it can quack. Like in case of iterable, it doesn't matter what it is, what matters is that you can iterate over it. You can as well rely inheritance if you want.