Friday, February 10, 2017

Just what does type in TypeScript do exactly?

Alight kids, this TypeScript...

enum ColorPattern {
   Solid,
   Splotchy,
   Striped
}
 
class Snake {
   commonName: string;
   genusAndSpecies: [string,string];
   lengthInMeters: number;
   appearance: ColorPattern;
}
 
class Constrictor extends Snake {
   poundsPerSquareInchPressureFromSqueeze: number;
   shouldYouRunIfYouSeeOne: boolean;
}
 
class HarmlessSnake extends Snake {
   recommendedAsPet: boolean;
}
 
class Viper extends Snake {
   lethalDoseFiftyScaleRating: number;
   milligramsOfVenomPerBite: number;
   shouldYouRunIfYouSeeOne: boolean;
}
 
type DangerousSnake = Constrictor | Viper;
 
interface AdviceDangerousSnake {
   give: (serpent: DangerousSnake) => string;
}
 
let advice = <AdviceDangerousSnake>{
   give: (serpent: DangerousSnake): string => {
      if (serpent.shouldYouRunIfYouSeeOne) {
         return `Run if you see a ${serpent.commonName}.`;
      } else {
         return `Freeze if you see a ${serpent.commonName}.`;
      }
   }
}
 
let tuple: [string, string] = ["Eunectes", "murinus"];
 
let anaconda = new Constrictor();
anaconda.commonName = "Green Anaconda";
anaconda.genusAndSpecies = tuple;
anaconda.lengthInMeters = 5;
anaconda.appearance = ColorPattern.Splotchy;
anaconda.shouldYouRunIfYouSeeOne = true;
anaconda.poundsPerSquareInchPressureFromSqueeze = 65;
 
alert(advice.give(anaconda));

 
 

...is pretty verbose considering that all it does is throw an alert with "Run if you see a Green Anaconda." in it, but, well, it's an excuse to show off TypeScript! TypeScript compiles to JavaScript and per https://www.typescriptlang.org/play/index.html it will ultimately make this JavaScript if the above is given...

var __extends = (this && this.__extends) || function (d, b) {
   for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
   function __() { this.constructor = d; }
   d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var ColorPattern;
(function (ColorPattern) {
   ColorPattern[ColorPattern["Solid"] = 0] = "Solid";
   ColorPattern[ColorPattern["Splotchy"] = 1] = "Splotchy";
   ColorPattern[ColorPattern["Striped"] = 2] = "Striped";
})(ColorPattern || (ColorPattern = {}));
var Snake = (function () {
   function Snake() {
   }
   return Snake;
}());
var Constrictor = (function (_super) {
   __extends(Constrictor, _super);
   function Constrictor() {
      return _super.apply(this, arguments) || this;
   }
   return Constrictor;
}(Snake));
var HarmlessSnake = (function (_super) {
   __extends(HarmlessSnake, _super);
   function HarmlessSnake() {
      return _super.apply(this, arguments) || this;
   }
   return HarmlessSnake;
}(Snake));
var Viper = (function (_super) {
   __extends(Viper, _super);
   function Viper() {
      return _super.apply(this, arguments) || this;
   }
   return Viper;
}(Snake));
var advice = {
   give: function (serpent) {
      if (serpent.shouldYouRunIfYouSeeOne) {
         return "Run if you see a " + serpent.commonName + ".";
      }
      else {
         return "Freeze if you see a " + serpent.commonName + ".";
      }
   }
};
var tuple = ["Eunectes", "murinus"];
var anaconda = new Constrictor();
anaconda.commonName = "Green Anaconda";
anaconda.genusAndSpecies = tuple;
anaconda.lengthInMeters = 5;
anaconda.appearance = ColorPattern.Splotchy;
anaconda.shouldYouRunIfYouSeeOne = true;
anaconda.poundsPerSquareInchPressureFromSqueeze = 65;
alert(advice.give(anaconda));

 
 

So there are few supertypes, if you will, in C#: class, interface, struct ...and TypeScript has a few supertypes too: class, interface, type ...a class being like a class in C# more or less, but an interface being very different. You can just make a variable from an interface as you can a class. The only differences are:

  1. that interfaces must be instantiated with a bit different syntax and at that moment all of the required fields must be hydrated and that's any field that doesn't have a question mark both immediately after its name (no space) and before the colon before its type
  2. a class can have methods but an interface only may have signatures for methods making them comparatively anemic

Classes may implement interfaces and what is more interfaces, yes, may implement classes. This will make your head want to explode until you eventually get used to the idea that "interfaces" are just a very different thing in TypeScript than they are in C#. An interface can be a contract to upcast a class to, but then again you don't really need a class to use them so don't think that's what they are for, all in all. There is no inheritance for type. You can just make a type like so:

type DangerousSnake = {
   commonName: string;
   genusAndSpecies: [string,string];
   lengthInMeters: number;
   appearance: ColorPattern;
   shouldYouRunIfYouSeeOne: boolean;
};

 
 

But the real intent of type seems to be to alias classes like so:

type DangerousSnake = Constrictor;

 
 

Both of the versions of DangerousSnake immediately above could replace this line of code at the beginning of our blog posting...

type DangerousSnake = Constrictor | Viper;

 
 

...and the code would still successfully compile to JavaScript and the "Run if you see a Green Anaconda." alert would still fire when JavaScript code was generated, but things get a little more interesting when we introduce a pipe symbol to alias two classes. This allows for something on par with "where T" in C#'s generics. The pipe means intersection and our type will have fields common across the two classes that hydrate it as seen at this screen grab from the TypeScript playground:

 
 

Replacing the pipe symbol with an ampersand allows for a union instead of an intersection and all fields across all classes are available. You will need to be a little more careful with these, obviously.

 
 

Addendum 10/4/2018: An interface in TypeScript will make nothing when compiled to JavaScript. It provides type safety in TypeScript and nothing more. Notice above that AdviceDangerousSnake does not exist in the JavaScript.

No comments:

Post a Comment