Type Script Inconveniences

Microsoft's TypeScript? may be providing several exciting ECMAScript 6 features early, but its type system makes a wide array of common JavaScript patterns exceedingly difficult to implement. Here are some examples I ran into while developing a custom business application:


Singletons

It's very easy to implement a singleton in JavaScript. Here's how you'd do it in a CommonJS module:

function Db() {
  // make methods, etc., in here
}
module.exports = new Db();

// then to use it elsewhere in the program: var db = require('db');

No muss, no fuss, no exposed constructor function that lets you break the singleton abstraction and create more instances of the same "class". Now, the equivalent code in TypeScript? looks like this:

class Db {
  // make methods, etc., in here
}
var db = new Db();
export = db;

// then to use it elsewhere in the program: import db = require('db');

Slightly less convenient -- TypeScript? doesn't let you use arbitrary expressions on "export =" lines -- but who's counting? Now, here's the problem: The type-checker rejects this outright! You won't be allowed to export the singleton, because it's using the "private type" Db. In fact, it's impossible to hide the detail that a class is used to implement the singleton behind-the-scenes, since the type-checker requires you to export the class too. You have to write this:

export class Db {
  // make methods, etc., in here
}
export var db = new Db();

// then to use it elsewhere in the program: import Db = require('db'); var db = Db.db;

Only slightly more verbose, sure, but instantly your abstraction is unavoidably broken.


Interop with "Extensible" Libraries

It's a very common JS idiom for a library to permit plugins and extensions by adding an object of your own to one of the library's built-in objects. jQuery does it. Knockout does it. I was using both in my app, and TypeScript? hates it. Here's what my Knockout extension looks like:

var signatureEmpty = "image/jsignature;base30,";
ko.bindingHandlers.signature = {
  init: function (el, valueAccessor) {
    var value = valueAccessor();
    $(el).jSignature({ height: 100, width: 400 });
    $("<input type='button' class='btn' value='Clear' />")
      .insertAfter(el)
      .click(() => $(el).jSignature('clear'));

if (ko.unwrap(value)) { (<any> ko.bindingHandlers.signature.update)(el, valueAccessor); } if (ko.isObservable(value)) { $(el).bind('change', function (e) { value($(e.target).jSignature("getData", "base30")); if (value() === signatureEmpty) value(null); }); } }, update: function (el, valueAccessor) { var value = ko.unwrap(valueAccessor()); if (value && value !== signatureEmpty) { $(el).jSignature("setData", "data:" + value); } } };

Pretty straightforward, idiomatic use of the library. Note that jSignature is a jQuery plugin, so this handily demonstrates the issue for both libraries. That issue is that TypeScript? rejects the above code, both the part assigning to ko.bindingHandlers and the part calling $.fn.jSignature, because its type-checker claims that those objects just don't have those slots (ko.bindingHandlers.signature and $.fn.jSignature respectively).

It makes some sense for the jQuery plugin, but absolutely none for the Knockout one: Of course it doesn't have that slot, because I'm defining it right now! The only way to get this stuff accepted is to add another file to your project called a "TypeScript? definitions file", which looks like this (I named it whoinventedthistypesystem.d.ts; this stuff had put me in a bad mood):

interface JQuery {
  jSignature(): void;
  jSignature(options: any): void;
  jSignature(command: string, ...options: any[]): any;
}
// Definition to permit us to define the 'signature' and 'diagram' custom Knockout bindings (the actual 
// definitions are in the file App/viewmodels/ReportEdit?.ts).
interface KnockoutBindingHandlers? {
  signature: KnockoutBindingHandler?;
}

With that above definition, TypeScript? is placated and accepts the file. But it's still overly restrictive. Notice the signatureEmpty variable? Initially, I tried to encapsulate that inside the binding definition like so:

ko.bindingHandlers.signature = {
  empty: "image/jsignature;base30,",
  // methods using it here
  // accessed as ko.bindingHandlers.signature.empty
}

But of course that was rejected, because KnockoutBindingHandler? objects obviously don't have a slot called "empty".

Basically, TypeScript? is seriously inconvenient when it comes to extending libraries.

Bonus: See that (<any>) thing in my definition of ko.bindingHandlers.signature.init()? That's what it looks like in TypeScript? when you cast to any, otherwise known as throw away all type information. Despite the ko.bindingHandlers.signature.update() function, to inspection, clearly having the right type, the type-checker rejected that too until I threw away that information!


Promises

TypeScript? doesn't understand them. It can't. It's just not expressive enough. Here's an example, taken from the aforementioned Db class:

public sync(): Q.Promise<boolean> {
  return this.IsAuthenticated?().then((result) => {
    if (result) return this.doSync();
    else return this.doLogin();
  });
}
private doSync(): Q.Promise<boolean> {
  return this.manager.saveChanges().then(() =>
    breeze.EntityQuery?.from('Report').using(this.manager).execute().then(() => true)
  ).fail(this.syncFailure.bind(this));
}
private syncFailure(message): boolean {
  return false;
}
private doLogin(): Q.Promise<boolean> {
  return this.Login().then((result) => {
    if (result) return this.doSync();
    else return this.syncFailure('Unable to Login');
  });
}

This'll be totally rejected by the type-checker! In particular, TypeScript? is incapable of comprehending the "flattening" aspect of promises, that being that a "promise for a promise for a value" is exactly the same thing as "a promise for a value". The only way to get this accepted is to delete the type signatures; if you have to delete the typesigs for standard idioms to work, why have a type-checker?


I won't speak for SystemsSoftware development. But from an application developer's perspective, you may be expecting/wanting more dynamic abstraction than the industry (typical organizations) wants. But this view repeats the debate in GreatLispWar. -t

[This is all from a custom business application, all rooted entirely in idiomatic use of JavaScript tools and libraries such as Knockout, jQuery, and Q (providing the promises). How is it "more dynamic abstraction" than would be wanted, considering nothing I present here is outside the recommended scope of the libraries I'm using? -DavidMcLean?]

VB, Delphi, etc. rarely needed anything like jQuery because it didn't have a fscked-up DOM.

[So? In your opinion, the DOM is messed up. Tools like jQuery and Knockout allow us to manage it in a structured, orderly way. -DavidMcLean?]

"Structured and orderly" compared to prior browser-based techniques, perhaps, but I see it as a band-aid. I'll agree that initial development in the "static OOP" way may be slow, but it usually made maintenance easier because the compiler would tell you what's missing and where it's missing. I'm actually a fan of GUI's as a service rather than app-language-specific API's, but that's kind of another story.

[Honestly, I think the DOM itself is pretty usable these days; standard DOM-manipulation methods like document.querySelectorAll() may not be as concise as jQuery, but "vanilla JS" is absolutely an effective means for DOM-manip now the APIs are standardised. None of that is the point, however. The point here is that TypeScript?'s stricter static type system makes simple and wholly idiomatic forms of expression, easy in JavaScript, completely impossible to use. -DavidMcLean?]

I question whether the NetworkDatabase-like approach of those is the best way to "do queries". Further, power can sometimes be a dangerous thing. Sometimes its really nice to be able to easily do something to every GUI element, but it also means that sub-app A may clobber what sub-app B is trying to do. The DOM is kind of a wild-west with few rules or regulations. It's great that your cattle can roam free, but it also may means somebody may be pooping in your stream (or what you thought was your stream) without your knowledge. Original coders may love it, but maintenance programmers tend to turn grey over cowboy gui engines/designs.

[The way to "do queries", as you put it, is appropriate to the domain; the DOM is a tree, so it's suitable to use selectors geared for expressing tree properties (like the descendant and sibling selectors). As for your "wild-west", why would you have two independent "sub-apps" active on exactly the same document? That's not a sensible design. -DavidMcLean?]

SeparationOfConcerns. You may want a fancy song player on the left side, and a fancy slide-show on the right side of the screen/page. If there is no reason why they should be linked, then the wall between them should be tall and strong to avoid cross-fudging of each other.

[Then put them in <iframe>s, or design them so they don't operate globally in the first place. It's not complicated. Heck, document.querySelectorAll() is a method; just call it on your widget's container element instead of on the document and you're golden. It's not like earlier graphical environments (VB, Delphi) could force you not to step on widgets in other parts of the window. -DavidMcLean?]

But you are relying on volunteerism to provide decent scoping. Sometimes you want it to be difficult to do "vast and global" things in code to discourage misuse. Yes, it is a form of "herding", but often that's just want the industry wants. And I realize that desktop VB/Dephi have problem areas, but in general they provided more civilization in the hands of average coders. As far as iframe, they sometimes have odd behavior in browsers that requires fiddling and fudging. They are not always treated like "native" widgets.

[I'm full aware that it requires effort on the part of widget designers to scope their widgets. That's why I also suggested <iframe>s, because they inherently provide a new DOM scope; those would be used in cases where a) you aren't writing your own widgets and b) the widgets you're using from other sources do in fact collide. <iframe> is primarily used to hold widgets from remote sites, in fact, and without much messing about as far as I can see; what fiddling do you see as necessary to make use of the tag for that purpose? -DavidMcLean?]

I cannot recall all the issues I've had with iframes at the moment; I just remember a lot of cussing :-) One I do remember is that they cannot participate in forms like a typical INPUT tag. Sure, one can hand-code such data gathering, but that's reinventing the wheel.

[That's to be expected. Did you want <iframe>s both to isolate their contents and not to isolate their contents, simultaneously? In general, scoping problems with the DOM are rare and of the developer's own doing; even with prefabricated widgets, the usual paradigm is that the dev "activates" the widget using a jQuery plugin call, so the appropriate selector may be provided. If that's insufficient, consider investigating the aforementioned Knockout, which provides a scoping system for associating DOM elements with viewmodel attributes; Angular and Ember provide similar support. -DavidMcLean?]

Perhaps the best approach is that specific widgets be required to explicitly "register" to participate in form-ness or other aspects, such as resizing, page-level read access, page-level write-access, etc. With "civilized" GUI kits, scope tends to be restricted by default, and one explicitly "grants" privilege for each widget to be accessed by other aspects. DOM is mostly the reverse in that it's wide open and relies on voluntary constraint. Yes, this creates "bureaucracy" up front, but reduces surprises in terms of longer-term maintenance. Further, I don't have much control over what an organization wants as its shop standard GUI kit/platform. It would be nice if the default GUI engine of browsers had some taming built in. I may personally like Angular, for instance, (example only) but that doesn't mean my boss does. -t

[Well, that's getting into questions of security model, which VB widgets didn't actually cover either. It would certainly be nice to grant specific levels of access to particular areas of the code, but it's not a common feature of GUI kits on the whole and it's unreasonable to diss the DOM for lacking that capability. -DavidMcLean?]

It's not really a security model in the strict sense. I'd call it a "scoping model" for lack of a better term. And yes, VB had leaky problems, but it also had fewer built-in "mass manipulation" constructs. The JavaScript-related frameworks/tools/kits in general seem to take a WriteOnlyLanguage-like approach to GUI kit designs. They say, "see how easy it does stuff like 'PERFORM insert-an-action-HOF-here WHERE insert-a-filter-HOF-here'?" (The sample cross-butchers SQL and FP for the hell of it). Maintenance is a distant second concern/selling-point. That's a general problem with the open-source mindset in my observation. Their goals are out of sync with organization preferences. People still use such tools because they are free and available and allow initial production to go along fairly quickly, but create maintenance headaches in the process as the level of cowboy-code passes a breaking point or too many "clever tricks" pile up on top of each other. -t

[Firstly, "run this function for each item that matches this predicate function" is perfectly standard FunctionalProgramming, not a weird mismatch of FP and SQL. As for the "open-source mindset", open-source tools like Knockout don't count in your assessment of open-source tools because...? -DavidMcLean?]

I didn't claim such "mapping" was SQL and FP. Perhaps I need to reword that part. You interpreted it different than my intention.

And again, I don't claim that a JS-based GUI tool cannot provide some of the features described; it's just that it has to reach a certain critical mass to be practical for an organization in the medium or longer run, like Delphi and VB did. I know it's not necessarily a fault of the DOM or JS directly, but it's the lack of an appropriate tool for the industry in terms of both "style" and market/mind share. Builders of JS-related GUI tools are in part at fault because they emphasize initial coding and "clever abstraction" over longer-term maintenance and "team programming".

Open-source programmers like to hand out nukes to every foot soldier. -t

[jQuery independently might in your view encourage "clever abstraction" and speed of initial coding, but all of the more modern GUI tools (those I've mentioned, plus Backbone and some others) are designed specifically to give structure to your code and simplify maintenance. They're also all very popular. -DavidMcLean?]

I've barely heard of them. I question your use of "very". If and when they become as well-known as jQuery itself, then we are talking a new ballgame.

[You've barely heard of prepared statements. I question your experience. -DavidMcLean?]

I have reason to question yours also, but it would probably turn into another nasty FlameWar.

[I freely admit I have little experience in the domain of custom business applications. It seems you're lacking in experience in the domain of Web applications, however, which is significantly more relevant to this discussion. -DavidMcLean?]

About 80% of my web experience is intranet-side, which is less concerned about Sql Injection than Internet. I do validate SQL-destined fields, but use a more compact technique.

[Regrettably, yes, it's common that intranet app design pays a lot less attention to important issues such as SQL injection. Prepared statements remain the most appropriate technique for passing any and all user input into SQL queries, regardless of query complexity, whenever they are available. -DavidMcLean?]

This has been debated in other topics. No need to repeat it here. (I'll link when found...if I remember.)

[TopOnPreparedStatements would be relevant, I expect. -DavidMcLean?]

{FYI, for singletons, you can define a public interface in another file and cast your export as that interface, like this:

var db: iDb = new Db(); export = db;

Not saying it's perfect, but it'll preserve your abstraction (or even enhance it, since it's way easier to mock for tests when you have an interface).

-Jibz}

[Oh, neat, that didn't occur to me. It still doesn't quite match the same semantics as a "real" singleton, but when mocks are considered perhaps it makes more sense not to. It's definite that at least some of my friction with TypeScript? arises from the mismatch between typical dynamically-typed design approaches (with DuckTyping, prototypical flyweight objects, etc.), and TypeScript?'s Java/CeeSharp-esque static, defined-up-front approach; I'm a lot more familiar with the former and not all that experienced with the right techniques and strategies for using the latter effectively.

Still, the inability to type-check promises properly is just dumb, especially considering they're important enough to be an official part of ES6. -DavidMcLean?]


CategoryJavaScript


EditText of this page (last edited June 23, 2014) or FindPage with title or text search