Introduction
It’s only when you move from JavaScript back to another language that you realise some of its absolute pleasures. One of those has to be duck typing.
Background
I am currently working on a WPF app in C# that visualises and manipulates some structured building data in OpenGL and displays a couple of linked charts. In that one single app, its various components store and handle 3D point data in at least 6 different ways. Many of them are vector classes or structs, each with the exact same X, Y and Z properties, the exact same operator overrides and a whole host of methods that do exactly the same thing - but all are entirely incompatible and completely non-interchangeable with each other.
Firstly, the structured building data contains a Vector3
class which it uses to stores all position and direction values. Then OpenTK has its own Vector3
struct that you have to convert everything to in order to visualise it. These have to be converted again to flat arrays of floats
that are passed to and from the GPU by the raw OpenGL functions. Then ClipperLib has it’s own IntPoint
class which everything has to be copied to and from in order to union or difference polygons. To triangulate complex polygons with multiple holes, they have to be converted to and from arrays of three floats
that LibTessDotNet uses to represent each vertex. Finally WPF also has its own Point
and Point3D
structs that have to be used when creating WPF charts.
All of this represents a completely insane amount of totally unnecessary data conversion. If every one of those components simply used an array of three floats to represent each x, y and z value instead of their own classes, their core functionality could still be exactly the same, but vertex data could be passed freely between them, interoperability would go through the roof and pretty well all of that overhead would simply disappear. We could then use a single common static class to provide all the functions that work with those arrays such as add, subtract, multiply, length, dot, etc.
However, that approach is anathema to what C# encourages as you then couldn’t control how values are changed and by whom, or implement all that other fruit like private/protected properties, change notifications and syntactic sugar such as operator overrides.
But these are vectors. They simply store three single-precision floats. No-one really knows what kind or size of model they will end up being used in, so you can’t arbitrarily set any rules that limit the values they can store. Also, in any decent sized 3D model there are likely to be thousands if not millions of them, so you would never actually want to subscribe to every individual change. Thus, is the ability to write “A = B + C;” instead of “A = Vec3.Add(B, C);” really worth all of that conversion overhead?
Dealing with this in JavaScript
It’s not that JavaScript doesn’t suffer at all from this problem, but it just offers so much more flexibility when dealing with it. For one, if you expose fields or properties in a JavaScript class, it is virtually impossible to control how those values can be changed and by whom anyway, so you have to work out other ways to approach the specific issues that might create. In that way, there is no real benefit of using an object over an array, bar how they are indexed. Also, normal Arrays and TypedArrays are almost completely interchangeable in all but some very specific edge cases, so the language actually encourages the use of arrays over classes for things like points and vectors, even if you have specific type requirements.
So you still really want to write your own vector class. That’s okay because in most cases duck typing makes it possible to use your class directly with other similar classes. Take as an example something like the THREE.Vector3
class from the THREE library. If we look at the copy()
method of that class, we can see that, as long as your own class has x, y and z properties that contain numbers, you can use it as an argument to that method. In fact, your class could contain the entire construction documentation for a new Death Star, but that method doesn’t give two hoots as long as it also has x, y and z properties that contain numbers. The same applies to all but one or two methods in that whole THREE class.
copy: function ( v ) {
this.x = v.x;
this.y = v.y;
this.z = v.z;
return this;
},
Moreover, duck typing means that you can get the methods of one class to work on an instance of an entirely different one as long it has all the properties and methods that may be accessed during that process. In the code example below, the copy method of the THREE library is actually being used to add two instances of an entirely different class. Yes, incredibly stupid things could happen if you misuse it, so don’t misuse it. But what more could you ask for in terms of interoperability.
var vec1 = new EntirelyDifferentClass(100.0, 200.0, 300.0);
var vec2 = new EntirelyDifferentClass(400.0, 500.0, 600.0);
THREE.Vector3.copy.apply(vec1, vec2);
Unlike C#, the THREE library in JavaScript doesn’t have to know absolutely anything about your class. You just need to have the right properties. In fact, you could use 15 different classes if you wanted, but if they all have x, y and z properties that contain numbers, you’re still set.
Click here to comment on this page.