A while ago, I explained why I thought that Duck Typing was dangerous. More recently, Scala popularized a different type of typing called Structural Typing, which is often described as being “type safe Duck Typing”. Let’s take a closer look at Structural Typing and see if it delivers on this promise.
With Duck Typing, you send a message to an object in absolute darkness. You don’t know whether this object knows this message and you just trust the caller to have passed you an object that does:
def test(o)
log o.getName # Let's hope this will work
end
The code above might be broken, but you will only find out at runtime.
Structural typing allows you to be a bit more explicit about your expectations on this object. In the following Scala example, I declare that the object passed in parameter should have at least one method, called getName, that should return a String:
def test(f: { def getName(): String }) {
log(f.getName)
}
This doesn’t seem like much, but it does buy us a lot of type safety. For example, if I try to pass an object that doesn’t define a getName method, the Scala compiler complains:
type mismatch;
found : Test
required: AnyRef{def getName(): String}
I get the same error message if I try to pass an object that has a getName method that returns an int instead of a String.
From that standpoint, Structural Typing is indeed superior to Duck Typing and one of my major objections to Duck Typing (“it’s not type safe”) goes away. Structural Typing doesn’t solve everything, though. Here is a quick side by side comparison of the main techniques available:
Duck Typing | Structural Typing | Class/Interface/Trait (1) | |
---|---|---|---|
Type safe | No | Yes | Yes |
Can be automatically refactored | No (2) | Yes (3) | Yes |
Respects “Don’t Repeat Yourself” | Yes (4) | No (5) | Yes (6) |
- I’m conflating classes, interfaces and traits. They have different semantic, but mean the same in the context of typing.
- More details on the impossibility to refactor Duck Typed code can be found in the blog entry linked above.
- Any attempt to rename the getName method in either of the three locations (inside the class for the instance passed in parameter, in the method signature or inside the method body) will cause all the other locations to be properly renamed as well.
- Since you declare no types at all with duck typing, there is no repetition.
- If you need to declare more than one method that accepts a parameter with a getName method on it, you will have to repeat the entire (f: { def getName(): String } statement for each one.
- Since you declare exactly one type with a Class, there is no repetition.
As you can see, Structural Typing is doing pretty well and the only problem is with the violation of the DRY principle that it requires as soon as you need to use it more than once for a signature. This can be easily avoided if you simply avoid using Structural Typing in this case and instead, encapsulate the method in a Class/Interface/Trait.
Bad:
def test(f: { def getName(): String }) {
log(f.getName)
}
def toXml(f: { def getName(): String }) {
log("" + f.getName + "");
}
The repetition notwithstanding, the problem with the code above is that renaming one of the getName methods will not cause the other one to be automatically renamed as well since the compiler has no way of knowing that these methods are identical. In some way, it’s ironic that the so-called Structural Typing doesn’t preserve… the structure of the type we’re actually passing.
Better:
trait HasName {
def getName : String = { ... }
}
def test(HasName f) = {
log(f.getName)
}
def toXml(HasName f) = {
log("<log>" + f.getName + "</log>");
}
So, Structural Typing… good or bad?
I have mixed feelings.
Structural typings can be handy for occasional pieces of code that might not be worth creating a Class/Trait/Interface for, but as soon as you want to reuse the signature more than once or if that signature contains more than one method, you are better off creating a carefully named type that captures that signature and use it instead.
What do you think?
#1 by Brian Slesinsky on February 12, 2008 - 12:18 am
What about recursion? Things will get tricky for structural typing whenever there’s a method that returns an object of the same type.
#2 by Stephan Schmidt on February 12, 2008 - 12:39 am
I’d like to have structural typing for interfaces. So I can pass a Person object to printName(HasName name) when Person implements a getName() method. Along the lines of traits, but distinct because I don’t want to mix mixins (puh!) and interfaces. Structural typing with traits does mix the mixing concept (code) with the interface concept (contract)
Peace
-stephan
#3 by Deron Meranda on February 12, 2008 - 1:14 am
It is probably just a difference in definitions being used, but duck typing can be and often is *type safe*; it just may not be known if the method calls will succeed until run time. But I think I know what you intended.
Structural typing is of course nothing new, although its introduction in Scala means it may get some renewed attention, which is a good thing. I does seem to deliver a potentially happy middle ground, as you’ve observed, between full out strict static typing and no guardrails duck typing.
However there is a trade off you’ve not mentioned; structural typing doesn’t just add improved type safety to duck typing, it also reduces some of the power and flexibility. Risking a contrived example, consider where you want to accept any object which has either a walk() or a slither() method (but not necessarily both), and you determine which to call by invoking the number_of_legs() method on it first. The set of signatures of valid objects is now no longer a simple enumeration of must-have methods, but a more complex functional description. It may be possible to define a static structural type signature (Haskell?), but not with the simplistic signature system you’re describing, or without getting into mixins or interfaces or all that other static type system cruft that you’re trying to avoid. That loss of flexibility may be acceptable, but it is there.
The other thing to note is that structural type “safety”, as you’re describing as being static (compile time), could also be used dynamically as well. In many dynamic languages this could take on some form of introspection, occurring at the time of the function invocation but before the body of the function is executed. In Python for example, you could easily define a similar HasName decorator, and define your function such:
@HasName
def test(f) { log(f.getName()) }
It might not be quite as “safe” because it would be a run-time type check and not compile-time, but it could still reduce the number of test paths needed before discovering the type inconsistency, and it would provide the code documentation that I think you’re really trying to achieve. You may also want to look at Python’s proposed optional type system called “functional annotations” as another related solution – see PEP 3107.
[BTW, your comment submission form will not accept a valid URL in the URL field]
#4 by Henrik on February 12, 2008 - 1:53 am
I would do this:
trait Fun{
type getName = {def getName: String}
}
// by extending fun, needed has the getName type available
object Needed extends Fun{
def log(f: getName) = Console.println(“umm”)
def test(f: getName) = log(f)
class A{ def getName = “umm” }
test(new A)
}
#5 by Cedric on February 12, 2008 - 4:35 am
Hi Deron,
It seems to me your walk()/slither() example would be better captured by simple polymorphism: make the contract move() and have this method invoke internally either walk() or slither() depending on the object…
And you are right: I disabled the submission of http address in comments for spam reasons…
Thanks for the comment!
#6 by Alex Blewitt on February 12, 2008 - 5:22 am
You don’t even need to Extends Fun; you can import the type alias from the trait (or object) to be in scope.
#7 by Erkki Lindpere on February 12, 2008 - 5:50 am
As Hendrik said already, Scala allows structural typing to respect the DRY principle as well, by letting you define a type alias for the structural type:
type HasName = { def getName : String }
def test(f: HasName) = {
log(f.getName)
}
def toXml(f: HasName) = {
log(“” + f.getName + “”);
}
… or, you could define a trait, as you did, and an implicit conversion from a structural type to the trait:
trait HasName { def getName : String }
implicit def named2hasName(named: {def getName: String}) = new HasName { def getName = named.getName }
Deron, implicits combined with traits would solve your problem as well:
trait Mover { def move }
implicit def walking2moving(walker: {def walk}) = new Mover { def move = walker.walk }
implicit def slithering2moving(slitherer: {def slither}) = new Mover { def move = slitherer.slither }
def acceptMover(m: Mover) = { … }
Sure, this requires some extra typing (pun intended), but if you’re writing a reusable component, the couple of extra lines of code for this are probably entirely justified.
PS. I don’t think there’s any reason to mix a leg count into this any more, but I guess you could do that if you expected a class that has both ‘slither’ and ‘walk’ methods.
implicit def slithering2moving(slitherer: {def slither; def walk; def legCount: Int}) = new Mover { def move = … /* based on leg count */ }
#8 by Paul Barry on February 12, 2008 - 6:15 am
Type safety is a false sense of security for people who don’t know how to write unit tests.
#9 by Joshua on February 12, 2008 - 6:28 am
Paul, your comment sounds silly when its about an article by someone who authored a very popular unit testing framework. I think the point is that if you have type safety, it removes the need for some of the tests.
#10 by Paul Barry on February 12, 2008 - 8:54 am
Joshua,
First, I never said the author of this blog doesn’t know how to write unit tests. Second, I don’t think you need to write additional unit tests to do type-checking stuff. Those kinds of errors get tested in the process of testing the behavior of the application. Basically, I disagree with the theory that you have to write more tests when using a dynamically typed language, which is based on the idea that the compiler is giving you some testing for free.
#11 by rreed on February 13, 2008 - 1:38 am
Hi Cedric, any possibility of a “throw-down” of sorts between you and fellow googler Alex Martelli? You would argue against Python and Alex would argue for it.
#12 by Anonymous on February 13, 2008 - 2:29 am
Refreshing to see that the new fangled languages are catching up with C++ template facilities.
I must be getting old, but maybe there’s a place for us dinosours in this new era too.
#13 by Henrik on February 13, 2008 - 3:57 am
Alex, you made a good point. Importing is a lot more sane way to do that; trait extending was just what first came to my mind.
Torbj
#14 by Nat on February 13, 2008 - 4:11 am
Structural/nominative typing is orthogonal to dynamic/static typing.
A language can have dynamic, structural types (e.g. Smalltalk); static, structural types (e.g. Modula, OCaml); dynamic, nominative types (e.g. CLOS); or static, nominative types (e.g. Java, Haskell).
#15 by Moh on February 14, 2008 - 1:41 pm
But doesn’t Structural typing suffer from the same malady as Duck typing for the worst possible scenario: just because two objects have the same method, it doesn’t mean the *contract* for the method is the same. Would you blindly call the “shoot()” method on an object, regardless of if it’s a camera or a gun?
#16 by Anonymous on February 15, 2008 - 12:34 am
Isn’t structural typing perfectly supported in the concept oriented programming (a new but very promising term for me)?
#17 by Paul on February 27, 2008 - 11:41 pm
Hi Cedric,
I’ve looked at Scala, and in general I think it tries to solve the wrong problem. By the time you’ve extended the expressiveness of your type system to make things fully OO you end up with a bunch of meta-data that obfuscates your original code.
Some posters have spoken about enforcing contracts. Contracts have both syntax and semantics. Semantics are runtime behaviour which you will never be able to enforce via a static check. This is why Eiffel has all those runtime asserts. Betrand Meyer got this right.
My view is that you are better off removing all this meta-data from your code and placing it within dedicated contract specifications. This is what TDD/BDD does. A far more expressive type system then Scalas is available for Strongtalk:
http//www.strongtalk.org/
I like Strongtalk and the fact that the type annotations are optional. But comparing Strongtalks type annotations with well written BDD specifications, I would say that the BDD specs are a lot more readable and much better at expressing intent and enforcing contracts.
Trying to force all this on to the compiler in a manifest way is barking up the wrong tree IMO. A compiler will never be able to fully address runtime semantics.
Paul.
#18 by Daniel Furrer on August 13, 2008 - 5:45 am
I think structural typing doesn’t necessarily mean that you have to specify what functions you need a passed object to have. This could be (and is, see OCaml) done automatically using type inference.
#19 by Ricky Clarkson on July 6, 2009 - 5:47 am
I don’t think any Scala user actually uses structural types throughout their programs. For example, FileOutputStream and Socket in Java have no common type that contains close(), so if you wanted to write a ‘using’ method, mimicking C#’s feature, you could either write two methods, or use structural typing.
You use it where the other options are horrible.
#20 by Background Checks on May 24, 2011 - 9:55 pm
I guess structural typing is much better than the duck typing, and it is easier to use.
#21 by Ana on May 30, 2011 - 3:33 am
In my case, structural typing works for me better than duck typing.
#22 by qwerty on June 4, 2011 - 8:37 am
“Would you blindly call the shoot() method on an object, regardless of if its a camera or a gun?”
No but you don’t need a compiler to tell you that you are sending the shoot message on the wrong object. If you need a compiler for that sort of thing, please leave the industry.
Duck typing gives a huge advantage: Easy decoupling abilities. AKA “You often don’t need to write new code to meet new specifications”. That is huge.
#23 by Dave on June 8, 2011 - 3:32 pm
Sometimes it is appropriate to give up the safety net provided by static typing. Maybe you just want to explore an API without worrying too much about method signatures or maybe you’re creating code that talks to external components such as COM objects.
#24 by juries on June 19, 2011 - 6:32 pm
Why make typing tough . I’m not impressed using duck typing cause for me its complicated unlike structural typing. Well, I guess it all depends on the individual which he prefers.
#25 by Patricia on August 25, 2011 - 9:00 pm
Cool. I just tried it out and structural typing is indeed a lot better than duck typing.
#26 by Tashia M. Burton on April 1, 2012 - 4:06 pm
Structural typing is more effective than duck typing even though they are similar, I prefer structural typing.