With duck typing, the interface is implicit - if `f` only calls `a` and `b`, then that’s exactly what `o` needs to support.
Duck typing also allows more dynamic interfaces, like “`o` must support `a`, and if calling `a` returns true, it must also support `b`”.
# let f obj = obj#foo 1 2;;
val f : < foo : int -> int -> 'a; .. > -> 'a = <fun>
but it's generally not called duck typing.I think the more dynamic nature is really the crucial difference. In OCaml, if the function above had an if/else, and called #foo in one branch, and #bar in the other, the type of the object would be inferred as having both methods, and would enforce that at compile time. With duck typing, you could pass something that only has #foo, so long as the right branch is taken.
def foo(o):
if (b := o.a()) is not None:
b()
Because the original interface indicates that all "o"s must know about the link between "a" and "b", there's no loss of generality, but now there's a statically inferable structure: "o" must support an "a" that returns None or a callable.If you do a tiny modification to "def foo(o, a): ..." then this doesn't apply any more, and you're outside the realm of structural types and into the realm of dependent types, which means you must drink deeply of the static typing kool-aid and want to write type signatures that run the risk of being more complex than the code they describe.
(But I like to quote Conor McBride: "if you're not going to write types your type system has to be stupid enough a computer can do it", from https://www.youtube.com/watch?v=3U3lV5VPmOU&feature=youtu.be...).
I love how you can just return a custom object literal from a function and TypeScript figures out the type, without ever giving it a name. This lets you explore with pretty much the same flexibility and speed as with dynamically typed languages, and only after you shaped your code sufficiently well, you can decide whether you want to name some of the types and make everything a bit more "solid".
Duck typing as a concept was re(?)-popularized around the advent of ruby, so might make sense to talk from a ruby perspective: http://rubylearning.com/satishtalim/duck_typing.html
With ruby, most everything you interact with is a true object, ie. you can modify behaviour of almost all classes/objects by adding/removing methods, mixin, etc. It's not unheard of monkey patching Integer or Array classes, core parts of standard library and language. Thus, you could dynamically invoke any object with any methods, without regards to compile-time constraints or "types". Errorhandling thus delegated almost entirely to runtime beyond basic syntax parsing.
Golang furthers the notion of capabilities as shared method signatures, by loosely binding this into interfaces, and enforcing many errors by static type checks at compile time. It's interesting as a development away from inheritance and towards composition and loose coupling of static program components.
"Duck typing" can be implicit, if you can call quack() on this() without error, you (the programmer) assume it's a Duck. The language may make it explicit (loosely structural typed like in golang, or other variations), or implicit (like in ruby). There are varying degrees, but you should be able to have a collection of the "Ducks" and be able to invoke quack() on them all.
Can't really say I see the connection with reflection. If quack() fails, you can get an error, either at runtime or compile-time. So no reflection required, though using reflection you may avoid errors dynamically.
Structural Typing as a concept is much better defined though.
> Structural typing is a static typing system that determines type compatibility and equivalence by a type's structure, whereas duck typing is dynamic and determines type compatibility by only that part of a type's structure that is accessed during run time.
That sounds like static vs. dynamic implementations of the same thing.
Consider a generic "proxy" object that can be arbitrarily disconnected and reconnected to objects of different types. These can exist in any duck-typed runtime system. What is such an object's "type", even in the sense of its "runtime type"? There isn't one. There might be a sense in which it has an instantaneous type—a type it has as of a given program world-state—but that information is inaccessible to the runtime, since any probing it might do to ascertain this might also cause the object's instantaneous typing to change in the middle of the probing procedure.
(See also: the Universal Server in Erlang [ https://joearms.github.io/published/2013-11-21-My-favorite-e... ]. What is this server's "type"?)
A type system is something that can make guarantees about the behavior of a program in advance. In that sense, duck-typing isn't really a type system. It's just asking objects questions and then blindly trusting the responses you get. There's nothing but convention guaranteeing that an object that e.g. in Ruby implements `respond_to?` a certain way, will _actually_ respond to those methods when they're called. The object can lie. Which means you don't have a guarantee, and so you don't have a type system.
This, I think, is the main point of the parent comment.
I think the distinction (and overlap) come into focus when you think computationally: how is a type computed? Type semantics are determined by the type checker, and Go's interface types are checked at compile time. Duck type checking happens at runtime. There's no language support in Go for this, but the closest thing would be passing a bunch of empty interfaces around and using the stdlib's reflect package everywhere so that you never panic, but sometimes return a user-generated TypeError.
Sure, Go's interfaces allow clients to accept multiple concrete types, but that's checked at compile time, not runtime, and so it will never be duck typed.
package main
import "fmt"
type Dog struct{}
func (d Dog) NumberOfLegs() int {
return 4
}
func (d Dog) Genus() string {
return "Canin"
}
func main() {
var dog interface{} = Dog{}
fmt.Println(dog.(interface{ NumberOfLegs() int }).NumberOfLegs())
fmt.Println(dog.(interface{ Genus() string }).Genus())
fmt.Println(dog.(interface{ Happy() bool }).Happy())
}
This fails at runtime, as `Dog` doesn't have `Happy` method.https://play.golang.org/p/d0TkGTCZa2S
Runtime results in panic:
4
Canin
panic: interface conversion: main.Dog is not interface {
Happy() bool }: missing method Happy
goroutine 1 [running]: main.main() /tmp/sandbox251002434/prog.go:18 +0x200
Using interface casting, Golang do support "duck typing" in similar fashion as ruby's Object class.
Here, by convention, the instance of Dog doesn't support "type" Happy(), therefore fails at runtime.So golang is somewhat dynamic as well, although using interface{} is kludgy and one would want to avoid that and reflection whenever possible.
Interestingly, python has included structural subtyping in 3.8[1] as part of the typing module.
Structural vs. nominal typing is pretty well known in programming language design circles, but circulating that knowledge is a constant uphill challenge. One of the reasons many of us are frustrated with Go's designers were how quickly they threw this work under the bus as part of their initial marketing strategy, which kind of knocks away the ladder for anyone else who is curious to learn about this stuff.
AFAIK SML is nominal. OCaml has a structural subsystem in that its object system is structural. The vast majority of the language is nominally typed.
> It's probably more that most mainstream typed languages, like C, C++, Java, C#, etc. have mainly gone down the nominal route
It's not just mainstream typed languages. Almost all statically typed languages use nominative typing. Tt doesn't have any real drawback and it's just simpler to understand and work with.
A language like typescript would go with a structural system because it's a much easier bridge and sell from an object-oriented dynamically typed world.
I guess I was referring to how records are structural. Although granted tagged unions are nominal, and you can get nominal typing through modules. I was probably wrong in posing it as 'one or the other' - seeing as many languages have a mix of both. I'm definitely not saying that nominal typing is bad, it's just that it's nice to have the option to go structural if you want, and many have not known that this option exists.
functor F(S : sig val x : int end) = struct ... end
then I can apply F to any module that includes a field x of type int. I don't even have to name the functor argument: structure Foo = F(struct val x = 3 val y = 4 end)
Unions, on the other hand, have nothing to do with subtyping. In the union datatype foo = Bar | Baz
val foo1 = Bar
val foo2 = Baz
There is no subtyping. There is exactly one type in question, which is "foo", and no other types to form a subtyping relationship.You might be thinking instead about how some languages implement algebraic data types over a language like Java by replacing "foo" with a supertype and implementing the variants as subtypes. Scala and Kotlin are examples of this. Doing things this way allows you to do interesting things that are not possible in SML, such as typing for exactly the variant you expect.
Ocaml has always had, in addition to normal records, structurally typed records and polymorphic variants. It also has SML's structurally typed module system, though goes much further by allowing modules as runtime values.
interface I { void m(); }
class A { void hello() { ... } }
I i = new A()::hello;The example they have is not illustrative or explanatory at all.
class Duck:
def fly(self):
print("Duck flying")
class Sparrow:
def fly(self):
print("Sparrow flying")
class Whale:
def swim(self):
print("Whale swimming")
for animal in Duck(), Sparrow(), Whale():
animal.fly()
output: Duck flying
Sparrow flying
AttributeError: 'Whale' object has no attribute 'fly'
This moreso shows a dynamic typing issue.