# 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...).
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`”.