javascripttypesgoogle-closure-compileralgebraic-data-typesdisjoint-union

Tagged unions in closure compiler


I'm currently in the process of comparing the Google Closure Compiler and the Flow static type checker, in terms of expressiveness. What I like about the latter is that it apparently can represent tagged unions quite nicely. The manual gives this example:

type Result = Done | Error; // a disjoint union type with two cases
type Done = { status: 'done', answer: Matrix };
type Error = { status: 'error', message: string };

Is there a way to do something like this using Closure Compiler? That means some way to force certain proprties of an object to not only be of a specific type, but have a fixed value? And to use that value for type inference, to distinguish between different options in a union? I found no documentation to this effect.


Solution

  • There isn't an exact translation. Here's how I would write this:

    /** @enum {string} */
    var ResultStatus = {
      DONE: 'done',
      ERROR: 'error'
    };
    
    /** @record */
    var Done = function() {};
    
    /** @type {ResultStatus} */
    Done.prototype.status;
    
    /** @type {Matrix} */
    Done.prototype.answer;
    
    /** @record */
    var Error = function() {};
    
    /** @type {ResultStatus} */
    Error.prototype.status;
    
    /** @type {string} */
    Error.prototype.message;
    
    /** @type {Result|Error} */
    var Result;
    

    This is a very verbose syntax but provides the best type checking. Any object with those properties is assumed to match the type. Extra properties are allowed.

    There's a much shorter syntax with slightly different behavior: typedefs.

    /** @enum {string} */
    var ResultStatus = {
      DONE: 'done',
      ERROR: 'error'
    };
    
    /**
     * @typedef{{
     *   status: ResultStatus
     *   answer: Matrix
     * }}
     */
    var Done;
    
    /**
     * @typedef{{
     *   status: ResultStatus
     *   message: string
     * }}
     */
    var Error;
    
    /** @type {Result|Error} */
    var Result;
    

    There are several other ways to write these types as well. Which you choose depends on what you want checked. For instance, do you want a warning if you misspell (or try to add) a property? Do you want an exact match on both property names or types, or are extra properties allowed?