On Type Systems: Javascript is pleasant (got it right.)

In my new-found mission to try and change minds, I’m going to try and convince you why Javascript’s object/type system got it right (for a mainstream widely accepted language.)

I’ve always felt this, but  the moment one of my hard-core strongly-typed friends said this two weeks ago: “You know, when you think about it, Javascript makes a lot of sense,” I knew we’d reached a tipping point.

Site note: If you want to relate this to life-advice or self-help advice, go read this excellent blog post too: http://thelastpsychiatrist.com/2011/01/are_law_schools_lying_to_their.html
I don’t doubt for a moment he sincerely believes he is a lawyer, because lawyer for him isn’t a profession or even a job, it’s a label, a code word for a kind of intellectualism he wants for himself.  As long as “all of my friends see me as…” it was well worth the cost.  He didn’t study to become an attorney, he bought a back-up identity.

Composing my thoughts linearly on this subject is hard, but I’m going to try. Let’s begin with some classical examples in any introductory OOP class/textbook/course.

Every object is of a certain “type”, the logic goes. A car is also a vehicle. A vehicle is also a machine.  A boat is a vehicle.  An aircraft is a vehicle. Knowing this, you can now write targeted code that operates on all vehicles once and run it against any vehicle.

Furthermore, you can write code targeted to each of these specializations too. Code that only works on an aircraft. Code that only works on a car. Code that only works on a boat. Code that works on all machines.

All of this makes so much sense. It’s a good world view. You define your business model as a set of “objects” with “types” and then you write code against those types. Now you can extend your system by declaring new “things”, which might be vehicles, and once you declare them as being a vehicle, your vehicle-based code works against them instantly!

So far so good, right? See any problems with this? Now the obvious cases are easy.

  1. What if a car is also a plane? (Multiple inheritence). Then how do you determine which “drive” function is called? Etc. etc.
  2. What about a “Truck”? Is a Truck a subset of car or directly a subset of vehicle?

But these are copouts. Let’s address something FAR more real-life oriented, since OOP-people are all about real-life modeling.

Is your car exactly the same as it came out of the factory? I bet the brand new factory car’s “o.pedalToTheMetal()” has a different impulse response than on yours. I bet your truck’s “o.turnLeftIndicatorOn()” doesn’t always work. Mine doesn’t. I bet your “o.stopInTenFeet()” doesn’t always stop in ten feet.

Beyond the very academically cool problems that research papers are made of, like multiple inheritence and how to define a “type”, is the very very pragmatic problem none of these examples ever considers: After you drive your car out of the showroom, it is distinctly YOUR car. It stops meeting factory specifications almost instantly. It acts different. It behaves different. It performs different. A mirror is chipped. A wheel is scratched. A door has a dent. A window doesn’t quite roll open.

This is why your textbook never moves past the definition of simple objects. It truly doesn’t have the balls to follow through with that example as a real living breathing growing changing software environment would. What it doesn’t compare is that each individual car, over time, becomes a class over time. Thus you end up with a thousand confusing classes, repeated code, and a bunch of junk which you are forced to contain with the infamous “design patterns.”

What I’m saying is, those OOP analogies DO work, but OOP proponents were always too cowardly to take them to their logical conclusion. If by now you’re thinking Alan Kay got it right the first time around with Smalltalk, welcome to every functional programmer’s point of view.

Now for a moment lets focus on identity. This could get slightly philosophical in the sense of “Who am I?” – go read the blog post to ponder further. The takeaway from that article which is relevant to this discussion is: Do you care that someone *is* a doctor, or do you care that *they can remove your tumor*? How you think about this, determines greatly how you approach any desgin, problem and thus solution in life – and in programming.

Does it tell you more that a person “canDefendAgainstADUI()” than that they “areLawyer() && !areLawyerWithoutLicense()” (Because you know someone could be a lawyer, and not have a license to practice law.)

See what I mean about “identity”. I’m an engineer because I engineer things. When I’m scuba diving, I’m a diver. When I’m dancing, I’m a dancer. My “identity” is far less valuable in these situations than by “ability”. Whether I can perform a cross-body lead is far more relevant than who I am and the host of identities and sub-identities that define me.

If you’re casting for the part of Long John Silver, you’re looking for the condition “has one leg”, than you are looking for the identity of “One-legged Person”. It is easier to test, reason about, think about, program against, debug, maintain and in general understand and comprehend. It is direct. It is to the point. And it expresses *intent*. Last year I wrote a very popular article about choice of programming languages, and my key recommendation was: Pick the language that expresses your intent. Of all the things you can decide on – expressing intent is the ONLY one that matters.

If any of this makes sense this far, and I haven’t lost you, or pissed you off, then you’re already coming around to seeing Javascript in a better light. You’re still probably feeling a bit “icky” about the “mess”. You’ve never seen the mess, but you’ve heard of it. You remember Netscape Navigator from 5 years ago. You are thinking about lack of auto-complete. No proper structure. No proper “type”. Without a “class” how do you ensure things can be constrained and contained?

Ever since I learned programming, this is what I started to do in old BASIC:

DIM a, b, c

It is literally the first thing you do before you can do anything useful. You have to define a name, and assign it a “type”. Then all is well, because the “type” protects you against doing something rash.

In the real world though, types can be a problem. When you have a program linked across three versions of the same library, you can begin to see problems. What one programmer thought is “APerson”, is not the same thing as what another programmer thought it was, which is still different from what a third one thought it was.

That’s where we realize why type systems are so painful: what a specific, particular, constructed, existing, working, living, breathing object can do for me is FAR more important than what it declares itself to “be”. I can declare myself a lawyer. For all you readers, you have no clue whether or not I “am a lawyer”. You might google my degrees, but you will never know that I *don’t* have a law degree. What you *do* know is that I am not bar certified in the state of Washington. Even by the most simplistic modeling angle, modeling behaviors is far more relevant than modeling identity. Incidentally, it also happens to be simpler. Go figure.

Wait a minute you ask – you’re just about thinking of interface-enforcement. Good job! You’re about to counter me with this: “What if I wrote a piece of code that should take in an object of an interface to, let’s say sort. One object might implement ‘Sort()’, another might implement ‘sort()’ while yet another might implement ‘doSort()’ and yet another might implement ‘performSort()’. Doesn’t a class/interface help me enforce consistency?”

That is a very astute observation and counter-challenge to my argument. There are two distinct responses I have to this.

My first response is based on group theory and equivalence classes – funny how I’m using classes to argue against classes. Look at it practically. Does a class REALLY help you enforce consistency? Think about it deeply to the last time you really did this and when it wasn’t toy software you wrote in your spare time where you were the sole contributor.

In my experience, as I’m sure it has been in yours too, classes can “confirm” whether an object implements your “Sort” method, but it can’t enforce shit. When you wrote your subclass, either you were aware of ISortable and decided to implement it, in which case you knew what your consumer wanted – which makes it no more or less complex in javascript. Or you didn’t know explicitly exposing ISortable would come in handy, in which case you’re doubly screwed in your classical architecture. Because you just entered refactoring hell of the deepest rung, my friend.

Furthermore, you can’t encapsulate context for shit! Now you’re doing globals and thread-locals and all sorts of nonsense. Then you’re doing locks and synchronization to deal with it. Then you’re doing async and continuations to deal with the locks! Then you’re using frameworks to deal with those callbacks and what not. Then you’re using IDEs to help you use those frameworks. Why? Because fundamentally you couldn’t pass around behaviors to save your life.

The correct (better) way of passing an object to a consumer is to never really pass an object. Hardly any consumer cares about your structure. It cares about performing an action. It is FAR easier to pass it in an anonymous function called “bookAirplaceTicket”, than to pass it in 10 objects called, “(ticketParams, context, customerInfo, databaseConnectionString,…. etc. etc.)”

Behind your function the consumer doesn’t care that it’s a toy function, a mock function, a stub or uses 10 objects or 500. You don’t care to tell your consumer about a thousand interfaces.

With me so far? Haven’t lost you? Is any of this making some sense? Then let me give you my second response, which is rhetorical.

What about “a Lawyer” who doesn’t want to prosecute? Who cares “how long”, or “why”. The classical approach is to give the lawyer a bunch of attributes. “OnVacation”, “VacationDuration”, and so on. Then every other person has to understand what a lawyer’s vacation means. And he has to check it. And if someone forgets to, they just booked themselves a useless lawyer. What if a lawyer broke his kneecap? Add another attribute: “IsKneecapIntact”. Then tell each human every intending to deal with a lawyer to check THAT condition.

Why? All because you refused to check whether the person “bool canProscecute()”. Because you insisted on getting all philosophical and typing him as “A Lawyer” more than you cared what you want out of him – which is a person who is currently capable, qualified and intends to prosecute.

How does this apply to OOP? Ever use an object that throws “NotImplementedException”? What does it mean? How is the object “Bus” if “Drive()” throws “NotImplementedException”? Is there a non-drivable Bus? It’s a tin can – I’ll go with you that far. But do you care that you have a “Bus”, or do you care that you have a thing that is capable of being driven and can carry a certain payload?

So now, not only do types not tell you something about an object, but you’re actively fighting them – checking for nulls or nils or exceptions or what not – when all the object had to do, was remove it’s method/function/behavior/message-handler called “drive”, and you would have stopped caring. Because it calls itself a Bus, and because the language insists that a Bus always have a method called Drive, you now have to execute it before you know it’s going to work. Surely that’s a terribly way to write code that can be reasoned about.

But wait you say – you have frameworks and static analyzers and expensive tools to prevent people can’t do that. You my friend have solved an NP-hard problem – you have successfully statically analyzed a program, using another program, and assured me it terminates.

I hope I’ve provided some “real life” examples on why even modern type systems fail, why, when you stop trying to implement your classical type system in Javascript, you actually end up with a much better, cleaner, simpler, direct, to-the-point and relevant methodology to write code. Depending on Javascript to “enforce” your “Type” will never work. The question is – what did your “Type” enforcement actually guarantee to YOU in your other language? Don’t tell me what you *think* it did. What did the language itself guarantee when it said an object is of “A Type”? What was the mathematical boolean predicate or premise that you could infer beyond any doubt, when an object was of “A Type”? Now how many premises did you actually assume when you designed your software?

Is it surprising that software is a nightmare to maintain? We live on assumptions/premises that were never guaranteed, enforced or validated. Because 99% good citizens always wrote things a certain way we made assumptions that were never true. Then we call someone a “bad programmer who doesn’t know C/Java/Ruby/Python/whatever” when they don’t follow a pattern we like – but it was never a guarantee given to you by the language. Javascript was simply ballsy enough to admit it and own it.

 

Advertisements

3 thoughts on “On Type Systems: Javascript is pleasant (got it right.)

  1. As far as I can tell you’re arguing in favor of what in ruby-land we’ve always called Duck Typing. Ruby is similarly not typesafe, and the language generally follows the principle of “if it quacks like a duck” it is a duck. In other words:

    def make_a_duck_quack(duck)
    duck.quack()
    end

    That’ll accept anything that implements a .quack() method, and it doesn’t have to inherit from Duck or implement the DuckableInterface in order to quack.

    There’s a whole lot of good to this, which is that you get out of the Java AbstractFactorBeanImplKillMe problem.

    On the other hand, holy fuck can you have some horrible bugs caused by having the type of an object shift out from under you. Its not normally a problem but you can run into an issue where a `@uri` instance variable might be either a String or a URI. This is slick until your code suddenly hits a snag where it really goddamn matters if its a String or a URI and you have to coerce it into a URI, but other code in your object demands to be able to inject a String into that object on a redirect, which may be a relative location and not possible to render into a URI object because of the standard library. And holy shit do you wish there had been some type enforcement on that variable when the class started out being written.

    https://github.com/chef/chef/pull/1827

    I’m not even sure I remember the description of that issue right now, but at the root is the URI-vs-String problem.

    And a lot of what you describe about inheritance is the LSP:

    https://en.wikipedia.org/wiki/Liskov_substitution_principle

    Part of SOLID:

    https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

    And yeah, inheritance makes for really shitty code sharing. There’s examples all over in the chef codebase where inherited classes override functions in the parent class and fundamentally break the contract the superclass sets up because it turns out that an ‘apt’ provider really isn’t a ‘dpkg’ provider with a bit of extra functionality and a slightly different command to shell out to.

    But you can also have confusing issues with duck-typing “contracts” as well, though. Consider ‘dog.bark()’ vs. ‘tree.bark()’ and some function `consume_barker(obj)` that calls `obj.bark()` — doubt that function handles bark in the context of both dogs and trees.

    There’s also the problem that for documentation you start absolutely needing type information. In the chef code base there are so many times that you see “def method(cookbook)” and its like… what the fuck is this? is this a Chef::Cookbook, or a String with the cookbook name, or maybe its the path to the cookbook on the disk? Fuck if I know, I guess I’ll go plowing through the source until I stumble across a ‘cookbook.name’ call and I’ll guess its the object model version… hope I’m not wrong…

    So anyway what we’ve started doing is documenting all our type information in our documentation anyway:

    https://github.com/chef/chef/blob/92bec4bb96d857f027068fc56af46c76c484fd50/lib/chef/resource.rb#L363-L367

    Once I start down the road of typing up all that documentation, why not do something with it? At the very least it would be nice if the spec tests ran in debug mode where the object types were validated on method entry and setting/getting instance vars and an exception thrown if they don’t match. Because if I said something is a ‘URI’ and something writes a String into it during the tests, I’d like to know about it because that means at a minimum that my docs are wrong, or else either my code or my tests are incorrect…

    So, yeah Inheritance is fairly awful. A dynamic duck typing free-for-all, though, can make for a messy, hard to read codebase, with many hard-to-reason-about bugs. I’ll take duck typing over Java any day, though, its vastly faster to code. At the same time, though, I’m fairly certain that there has to be some kind of happy middle ground.

    At some point I need to check out erlang (or elixir) and its dialyzer which from what I gather is an untyped functional language, but because its functional when you add type annotations the dialyzer can do proofs which can catch type errors. Which seems like magic because the Strongly Typed folks claim that you can’t do that kind of thing unless your entire codebase is 100% Strongly Typed by your compiler.

    The Ruby language is also starting to be more interested in type safety and adding some type annotations, which is another type-compromise kind of approach:

    https://www.omniref.com/blog/2014/11/17/matz-at-rubyconf-2014-will-ruby-3-dot-0-be-statically-typed/

    Anyway, I think fully dynamic duck typed languages without even any opt-in type annotations are actually not ‘Right’ and are more like about to sprout various kinds of type annotations as we’ve now built some large-ish programs in those languages and seen how they become unpleasant to maintain.

  2. I explicitly avoided the word “Duck Typing”, because it still comes around to “Typing”, and people end up getting touchy over it.

    But yes, I was indeed promoting Duck typing. See a Duck could be a mutant-hybrid-with-a-Turtle. A Duck might be called a “Badak” in my native language. What it’s called is far less relevant, if you had the actual object in your hands, than that you god damn have THE OBJECT. Stop being so stuck up your own ass that you are looking at a god damn duck, you can see it quack. You can see it swim. But holy cow I called it a “Badak”, and now you’re going to throw a fit at me and get all pissy?

    You can be AS STRONGLY typed as you want. But worry about the type of the OBJECT you HAVE! You HAVE IT! RIGHT THERE. Why the hell do you care what someone insisted on naming it three library-dependencies away, when you could assert that what you *hold* meets your criteria? Golang got it right with their interfaces.

    function foo(param): enforceTheEverlovingStrongnessOfType(param) {
    //function code
    }

    Yup. Erlang/Elixir would be my ideal language for the world. But we don’t always get what we want.

    There’s a whole body of work under lambda calculus about how having a few, specific, fixed set of structures – which are easier to introspect and reason about – provides better guarantees than Classical typing. It’s another big reason Javascript ends up being so powerful. The internet’s data-interchange format also happens to be how it represents literals. So {“Foo”: bar} isn’t to be parsed “into” something else. It *is* a declaration of that object you’re passing around.

    Even if someone didn’t document your object, you can transparently view the entire structure. The whole thing. Not just through IDEs, but as a contract of the language. Because the language isn’t trying to hide where it came from.

    That’s another big argument/rant I want to get into. If you want to hide, then use the Actor model and never expose. Passing an object around that’s going: I have these public methods – but I have hidden ones too, that I won’t tell you about.

    Well that’s a shitty way of hiding data. The worst!

  3. Also, since you did quote SOLID, I’m arguing specifically, that EVERYONE in strongly-typed-land is terrible at implementing the Open/Closed principle at all. Most design patterns are case studies on how to utterly avoid Open/Closed.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s