Blog-Archiv

Dienstag, 22. Mai 2018

Call Overridables From Factory in TypeScript

You shouldn't call overridable methods from constructor. In my last Blog I demonstrated how this can lead to unexpected results due to uninitialized instance fields. In this Blog I want to show a way to circumvent this problem, without giving up the protected abstract initialization call to be implemented by sub-classes.

Replace constructor() by init()

The first step is to move the logic done in constructor to a protected init() instance-method. This init() is meant to be called by a static factory. To avoid the usage of (implicitly public) constructors from outside we define them all as protected, so that they can be called by their own class and sub-classes only. Here is the rewritten Name class:

Name.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
export abstract class Name
{
    private value: string;

    /** This is just to protect construction and allow factory only. */
    protected constructor() {
    }
    
    /** Object initialization, to be called by factory. */
    protected init(): void {
        this.value = this.getDefault();
        // all other initializations go here ...
    }

    /** Sub-classes must define a default. */
    protected abstract getDefault(): string;
    
    /** Expose the value readonly. */
    public getValue(): string {
        return this.value;
    }
}

The init() method now does what the constructor did before. The constructor is now proctected.

Add Static Factory

The rewritten concrete FirstName class provides the static factory. (This is NOT the "Factory Method" design pattern!)

FirstName.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Name } from "./Name.js";

export class FirstName extends Name
{
    /** Static factory replaces the constructor. */
    public static newFirstName(): FirstName {
        const firstName: FirstName = new FirstName();
        firstName.init();
        return firstName;
    }
    
    public readonly defaultValue: string = "(No Firstname)";

    /** This is protected to allow construction through factory only. */
    protected constructor() {
        super();  // compiler requires this!
    }
    
    protected getDefault(): string {
        return this.defaultValue;
    }
}

The public static newFirstName() is the factory. It calls the init() method after construction, so that all sub-classes must have finished their field-initializations. If you put all initializations inside that, there will be no problem any more with undefined fields.

When a factory is present, construction should not be possible from outside, thus all involved constructors must be protected or private.

Mind the implementation order: statics first, then instance fields and methods.

The bad news is that you have to repeat the static factory code on any concrete sub-class, e.g. LastName.

Test It

But before we think about that code duplication, let's test the new solution.

test.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { FirstName } from "./FirstName.js";

declare function title(assertTitle: string): void;
declare function assert(criterion: boolean, message: string): void;

title("Call Overridables From Factory");

const firstName: FirstName = FirstName.newFirstName();
assert(
    firstName.getValue() === firstName.defaultValue,
    "firstName.getValue() is expected to be '"+firstName.defaultValue+"': '"+firstName.getValue()+"'");

// following must NOT be possible when factory exists!
//const firstName2: FirstName = new FirstName();

Now the factory is used to create the FirstName object instance. The assertion makes sure that the default is estimated correctly.

When compiling the sources with

tsc -t ES6 *.ts

and running the test (in the test.html page I introduced in a passed Blog), we can see:

Call Overridables From Factory

firstName.getValue() is expected to be '(No Firstname)': '(No Firstname)'

Now the test is green!

Conclusion

Use a static factory whenever you need overrides already at construction time.

Is it possible to avoid the code duplication of the static factory? Maybe by using generics? There is even more code duplication, because both the factory and the init() method take over constructor responsibilities, therefore all construction parameters may have to be duplicated to both of them. Material for my next Blog.




Samstag, 19. Mai 2018

Don't Call Overridables From Constructor in TypeScript

Do not call overridable methods from constructor. This is a general risk-mitigation rule for most of today's object-oriented languages, among them Java and TypeScript. Not so widely known, but quite subtle and hard to understand.

In TypeScript, overridable methods are public or protected. In Java you could avoid the pitfall by making these methods final, but there is nothing like that in TypeScript. Now, what exactly is the pitfall?

Example

The problem occurs not always when you call an overridable method from constructor, just when that overridable uses instance fields of its own class that are expected to have been initialized to a certain value.

Following example classes should make this clear. They represent the idea of encapsulating a Name string. Let's assume that FirstName and LastName are both names, but with different semantics, one being a different default value. (I left out all other semantics, so the classes may not look really useful.)

Name.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
export abstract class Name
{
    private value: string;

    constructor() {
        this.value = this.getDefault();
    }

    /** Sub-classes must define a default. */
    protected abstract getDefault(): string;
    
    /** Expose the value readonly. */
    public getValue(): string {
        return this.value;
    }
}

The abstract super-class Name leaves it up to sub-classes to define a default-value for the encapsulated value string. It does so by declaring a protected abstract getDefault() method. Mind that TypeScript, like Java, requires a class to be abstract when it contains an abstract method, and sub-classes are forced to implement it, or be abstract again.

FirstName.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { Name } from "./Name.js";

export class FirstName extends Name
{
    public readonly defaultValue: string = "(No Firstname)";

    protected getDefault(): string {
        return this.defaultValue;
    }
}

FirstName extends Name. The programmer decided to define the default in an instance field constant. Looks like nothing can break that code, right?

LastName.ts
Click to see LastName, which is exactly the same, just with a different default-name.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { Name } from "./Name.js";

export class LastName extends Name
{
    public readonly defaultValue: string = "(No Lastname)";

    protected getDefault(): string {
        return this.defaultValue;
    }
}

Test

Finally here is a test for these quite simple looking classes. You can execute it using the HTML page that I introduced in a recent Blog. Script references are on bottom of the page.

test.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { FirstName } from "./FirstName.js";
import { LastName } from "./LastName.js";

declare function title(testTitle: string): void;
declare function assert(criterion: boolean, message: string): void;

title("Don't Call Overridables From Constructor");

const firstName: FirstName = new FirstName();
assert(
    firstName.getValue() === firstName.defaultValue,
    "firstName.getValue() is expected to be '"+firstName.defaultValue+"': '"+firstName.getValue()+"'");

const lastName: LastName = new LastName();
assert(
    lastName.getValue() === lastName.defaultValue,
    "lastName.getValue() is expected to be '"+lastName.defaultValue+"': '"+lastName.getValue()+"'");

This first imports the classes to test. Then it declares two external functions that are provided by test.html (thus it depends on its test-executor). It outputs a title, and then constructs FirstName and LastName objects, executing the same assertion on both: check that getValue() returns the correct default value.

Mind that, due to the imports, all TS files must be in the same directory. You can compile them by:

  tsc -t ES6 *.ts

Would you expect that the test succeeds?
When you load the test.html page into your browser, you will see this result:

Don't Call Overridables From Constructor

firstName.getValue() is expected to be '(No Firstname)': 'undefined'
lastName.getValue() is expected to be '(No Lastname)': 'undefined'

Both tests failed because the real value was undefined instead of the expected default name!

Executing an override before its owning object was initialized

The explanation of this pitfall is the object-initialization control flow.

  • A sub-class calls the constructor of its super-class before its own instance fields have been initialized. In other words, firstName.defaultValue is still undefined when the Name constructor starts to work.

  • The Name constructor calls the getDefault() method, which is overridden and thus control goes back to the FirstName object.

  • The getDefault() method in FirstName returns the value of the not-yet-initialized instance field this.defaultValue, which is undefined.

  • The value field in Name now has been set to undefined by the Name constructor. That was the pitfall.

  • As soon as the Name constructor has terminated, the object-initialization of FirstName gets started, before constructor execution. Now "(No Firstname)" is assigned to the instance field firstName.defaultValue, but too late for getting into super.value.

Conclusion

There are several ways to fix this. One is to provide the default value as static field. Another one is to hardcode the default inside the getDefault() override implementation. The basic problem is the initialization order of object instances. Constructors are not really object-oriented, they are a legacy from structured languages. Whatever is inside a constructor is hardly overridable.

In my next Blog I will show how this can be fixed without static fields or hardcoding values inside methods.




Donnerstag, 17. Mai 2018

No Private Names in TypeScript Classes

So we have been seduced to believe that TypeScript is very similar to Java. The longer I try it out the more I find out that it is not.

TS gives us access modifiers similar to Java:

  • public (visible for all, the default, a property or method without an access modifier is automatically public)
  • protected (visible for the own class and for sub-classes, other than in Java it is not visible for classes in same directory)
  • private (visible just for the own class)
  • readonly (instead of const inside classes, resembles Java final)

The Java default access modifier (package-visibility, active when no modifier was defined) is not present in TS, because no package-concept exists.

Private Is Not Quite Private

Access modifiers promise something that never has been standardized. Thus the Scala programmer may expect something different than the C++ or Java programmer. Let's try out private in TypeScript.

I know, it is ridiculous to derive FlagShip from Flag, but look how I defined private properties in these classes:

Flag.ts
export class Flag
{
    private readonly defaultValue: boolean = false;
}
FlagShip.ts
import { Flag } from "./Flag.js";

export class FlagShip extends Flag
{
    private readonly defaultValue: boolean = true;
}

Try to compile this by

tsc -t ES6 *.ts

You will see following error:

error TS2415: Class 'FlagShip' incorrectly extends base class 'Flag'.
  Types have separate declarations of a private property 'defaultValue'.

So not each of the two classes can have its own private defaultValue property? What kind of privacy is this?

However, we must fix it by thinking about some useful name that can replace defaultValue in class FlagShip. I changed it to be shipDefaultValue.

But, to challenge fate, I added two private methods of same name now.

Flag.ts
export class Flag
{
    private readonly defaultValue: boolean = false;
    
    private getDefaultValue(): boolean {
        return this.defaultValue;
    }
}
FlagShip.ts
import { Flag } from "./Flag.js";

export class FlagShip extends Flag
{
    private readonly shipDefaultValue: boolean = true;
    
    private getDefaultValue(): boolean {
        return this.shipDefaultValue;
    }
}

Another compile error shows up:

error TS2415: Class 'FlagShip' incorrectly extends base class 'Flag'.
  Types have separate declarations of a private property 'getDefaultValue'.

So, getDefaultValue is a "property"? Isn't this called "method", or at least "function"?

What we learn is that private in TS means invisible to the outer world, but not to the compiler.

Conclusion

If something is quacking, it is not necessarily a duck. I will explore TS access modifiers further in future Blogs.




Samstag, 12. Mai 2018

Why Short Is Not Always Good

Yes, I know, "TL;DR;" means "Too Long, Didn't Read". Kilroy is everywhere. Nevertheless, this long long long Blog is about the (still famous?) saying

Some people, when confronted with a problem, think
“I know, I'll use regular expressions.”
Now they have two problems.

which has been exhaustively discussed on Coding Horror and Jeffrey Friedl's Blog. Here is a history of this epoch-making joke template:


Whatever you solve by regular expressions will break soon, because they are imprecise assumptions, mostly unreadable. The best explanation of the problem with regex on Jeffrey's Blog was

It’s hard to distinguish between the data and operators.

That's the point, well said!
By the way, doesn't that somehow also fit to C++ operator overloading and Scala symbols as function names?
To me it looks like the common goal of all these constructs is to make source code shorter!

Less Code Less Bugs?

The shorter the code, the fewer mistakes are inside.

This may be true in some cases. In other cases the mistakes will not be less, just sit deeper, being more invisible, much harder to find. Especially when the code builds upon implications that are known to experts only, like it is with Domain Specific Languages. Regular expressions is a DSL, and many dialects of it exist.

But to achieve shorter code you need programmers that avoid copy & paste coding and agree on shared code, not regular expressions or a better general programming language!

There is also an addiction to use abbreviations and acronyms everywhere (see jQuery API). Lazyness? Need for speed? In old times there were reasons for shortness, memory and disk space was low. Today these restrictions are gone. A short class-, field-, function- or variable-name is much more error-prone than a long one, because it will not be understood, or misunderstood. It's time to learn the art of being precise without being too long. Don't slaughter words, use them.

Write Less Do More?

This is the slogan of jQuery, a JavaScript library that (besides many other things) provides CSS expressions to access DOM elements in an HTML document, something that HTML5 meanwhile also provides via element.querySelectorAll().

If you read code that uses jQuery you will be reminded of regular expressions. It is the same concept: access a structure by duplicating its identifiers. When the structure changes, because the designer edited the HTML, something doesn't work any more. If you need to fix the resulting bug, you may be confronted with hundreds of jQuery expressions that all point to certain places in the HTML, just one of them fails because it points to something that doesn't exist any more. How find something that doesn't exist any more? Didn't maybe the author write less at the expense of the maintainer that has to do more now?

Get Rid of Boilerplate Code?

"Boilerplate is that part of a form which does not change from one form to another."

Can we agree on this? So then, let's look at some web form: on top is the label "Date of Birth", below is the date-chooser. Do we really want to get rid of the label, which obviously doesn't change from one form to another? How can the user then know that it is "Date of Birth"?

Some boilerplates like the C #ifndef statements are for sure dispensable. But isn't this an ancient language that has been replaced already by things like Java? So why talk about it? Rewrite your software to a modern language!

Doesn't also Java have boilerplate? For example, everything has to be inside a class, but not always we want to express something through a class.
Yes, true. But now it's going to be cultural. I prefer the object-oriented culture, functional languages are not for the masses, structured languages (C, JavaScript) are outdated. Code reuse by inheritance is elegant and efficient, and lacks the boilerplate of delegation, which in fact is code duplication of the public interface of the delegate. If you show me a programming language that expresses the

Object-Oriented Paradigms
  1. Classes: Data and methods that work on them are grouped together in classes

  2. Polymorphism: An interface is an outline of concrete classes that lists some methods they have in common

  3. Abstraction: An abstract class contains default implementations that can declare and call abstract methods implemented by sub-classes only

  4. Inheritance: A class can extend another class, and reuse and modify the public and protected behaviour of the super-class

  5. Encapsulation: Access modifiers like public and private let distinguish between internal implementation and its external interface, thus reducing complexity for the outer world

  6. Dynamic Overrides: At runtime, the method implementation of the most specialized class of an inheritance hierarchy is executed

  7. Method Overloading: You can have equally named but differently implemented methods foo(), foo(bar), foo(bar1, bar2) ... in a class, distinguished by their parameters

  8. Open Recursion: the this keyword provides access to methods and properties of the same object instance

with less boilerplate than Java, I will immediately change to that language. But don't fool me with old mistakes like preprocessors, operator overloading, or other tricks that give unlimited freedom of expression, or poor readability. And: for source code beyond 10000 lines of code there is no way around strict type checks! And: the runtime environment needs to be operating-system independent!

How do you want to get rid of boilerplate when needing to express all of the OO things above? Isn't it important to put everything into a function or class, because a code-block without a name won't be reusable?

Use Comments!

Surprisingly not a single contribution on Jeffrey's Blog mentions the possibility of disarming regular expressions by comments.

The comment, despised by the OO community, can shine here. What about commenting your regular expression? Can it be expressed also in human language? We need to know what it should do, and why it was necessary to use regex.

For jQuery CSS selectors we would like to know what is at the place where the expression points to. Is it the "Submit" button? Or is it the scroll-container of the table? This information is much more valuable than the CSS-class in the selector that may not exist any more.


Conclusion

Take a deep breath. Give your audience the time to think over what you just said. Don't go with those that are bored because they already know about everything. You want to explain it to those that never heard about it. We don't need to be that fast and short. Let's permit the part of boilerplate that is common sense, and reject just the purely technical workarounds.

When coding, you need some structures in which your solutions can live. Keep data and the functions working on them together. Give your solution a class name, or at least a function name. Use anonymous classes and functions just when their content is very specific to the surrounding code, and keep them short by calling properly named functions that are implemented outside.

Language designers compete for the shortest implementation of the quicksort algorithm. It is not important how many lines of code you need for that. It is important that everybody understands, just from reading the code, how quicksort works! Such a programming language will be used in publications of any kind, because it promotes common sense and human understanding.

Why is short not always good? Because what is short for one may become long for many others. And you will have to pay them all. So better do it well right from start.




Montag, 7. Mai 2018

Using TypeScript Compiler Options

Compiler options serve to sharpen the checks a compiler makes. The TypeScript compiler provides many such options. For example, when you set "strictNullChecks": true, you will have to fix following code:

function displayHello(elementId: string, displayText: string) {
    const element = document.getElementById(elementId);
    element.innerHTML = "Hello"+displayText;
}

The compile error message is:

error TS2531: Object is possibly 'null'.

It's about element. You have to fix it to more robust code:

function displayHello(elementId: string, displayText: string) {
    const element = document.getElementById(elementId);
    if (element)
        element.innerHTML = "Hello"+displayText;
    else
        throw "Did not find id "+elementId;
}

In this Blog I refer to a very useful Blog of Meziantou. I won't discuss the compiler options one by one, instead I will list those that I applied, and then show how it affected my 3D-geometry TS source that I wrote in one of my recent Blogs. I won't repeat the whole source here, I will show just the critical parts that had to be fixed.

Where to Put Compiler Options

You can call the tsc compiler with a list of files to compile on command line, or you can call it without any argument.
In first case you can give options directly in the command line, e.g.

tsc --strict *.ts

As all options are false by default, you don't need to give true or false on the command line, simply naming the option sets it to true.

In second case tsc will search in current directory for a file named

tsconfig.json

If it doesn't find such a file, it will search all parent directories upwards. So best is to put tsconfig.json into the root directory of your project, and compile your project with a simple tsc command in that directory. Beside compiler-options you can also list the files to compile inside tsconfig.json.

Many more things can be inside tsconfig.json. There is even a schema for it, although not so easy to read.

List of Recommendable Options

Mind that this is not the setting for a professional project but just a minimal one to sharpen the compiler.

{
    "compilerOptions": {
        "module": "ES6",
        
        "noImplicitAny": true, 
        "noImplicitThis": true,
        "strictFunctionTypes": true,
        "strictPropertyInitialization": true,
        "alwaysStrict": true,

        "allowJs": true,
        "checkJs": true,
        "noImplicitReturns": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true
    },

    "include": [
        "**/*.ts"
    ]
}

JSON is a JavaScript-like data format, introduced by Douglas Crockford (author of the famous book "JavaScript, the Good Parts"). It is used extensively in the web world for data-interchange and configuration.

Mind that you can not have comments inside JSON, neither "//" nor "/**/". Thus I will explain that JSON listing here:

  • The "compilerOptions" section is a JSON object. Every property inside is a compiler switch.
    The first group of switches could be replaced by a simple "strict": true statement, but for clarity I have listed them one by one.
    The second group is not contained in "strict".
    All the options listed here need to be configured explicitly, because their default value is false.

  • The "include" section is a JSON array. Every expression inside will be taken into the list of files to compile. Wildcards are allowed, thus I can simply refer to all files ending with .ts. The expression "**/" reads "including subdirectories of any depth".

A special case is strictNullChecks.

        "strictNullChecks": true, 

This sounds quite useful, but leads to a lot of maybe misleading default definitions for class properties. I had this active, but decided later to remove it, because it makes it impossible to use the JS language default undefined.

Following list of compiler-options fortunately already has the correct default and needs not to be set:

        "allowUnreachableCode": false,
        "allowUnusedLabels": false,
        "suppressImplicitAnyIndexErrors": false,
        "suppressExcessPropertyErrors": false,

But you should make sure that they were not changed explicitly to true!

Consequences

You may become desperate when you apply this to your project, and then see the list of errors. But it pays off to fix them all. Your code will be much better after.

Here is the list I got when I compiled my (incredibly simple) 3D-geometry TS sources with the options above.

interfaces/geometry/3D/BoxImpl.ts(7,14): error TS2420: Class 'BoxImpl' incorrectly implements interface 'Box'.
  Types of property 'distance' are incompatible.
    Type '(other?: Point3D | undefined) => number' is not assignable to type '{ (): number; (other: Point): number; }'.
interfaces/geometry/3D/BoxImpl.ts(31,28): error TS2345: Argument of type 'this' is not assignable to parameter of type 'Point3D'.
  Type 'BoxImpl' is not assignable to type 'Point3D'.
    Types of property 'distance' are incompatible.
      Type '(other?: Point3D | undefined) => number' is not assignable to type '{ (): number; (other: Point): number; }'.
interfaces/geometry/3D/BoxImpl.ts(34,9): error TS2322: Type 'BoxImpl' is not assignable to type 'Box'.
  Types of property 'distance' are incompatible.
    Type '(other?: Point3D | undefined) => number' is not assignable to type '{ (): number; (other: Point): number; }'.
interfaces/geometry/3D/Point3DImpl.ts(15,5): error TS2416: Property 'distance' in type 'Point3DImpl' is not assignable to the same property in base type 'Point3D'.
  Type '(other?: Point3D | undefined) => number' is not assignable to type '{ (): number; (other: Point): number; }'.
    Types of parameters 'other' and 'other' are incompatible.
      Type 'Point' is not assignable to type 'Point3D | undefined'.
        Type 'Point' is not assignable to type 'Point3D'.
          Property 'z' is missing in type 'Point'.
interfaces/geometry/3D/Point3DImpl.ts(17,13): error TS2322: Type 'Point3DImpl' is not assignable to type 'Point3D | undefined'.
  Type 'Point3DImpl' is not assignable to type 'Point3D'.
    Types of property 'distance' are incompatible.
      Type '(other?: Point3D | undefined) => number' is not assignable to type '{ (): number; (other: Point): number; }'.
interfaces/geometry/3D/Point3DImpl.ts(19,42): error TS2345: Argument of type 'Point3D | undefined' is not assignable to parameter of type 'Point'.
  Type 'undefined' is not assignable to type 'Point'.
interfaces/geometry/3D/Point3DImpl.ts(20,42): error TS2345: Argument of type 'Point3D | undefined' is not assignable to parameter of type 'Point'.
  Type 'undefined' is not assignable to type 'Point'.
interfaces/geometry/3D/Point3DImpl.ts(21,42): error TS2345: Argument of type 'Point3D | undefined' is not assignable to parameter of type 'Point3D'.
  Type 'undefined' is not assignable to type 'Point3D'.
interfaces/geometry/3D/Point3DImpl.ts(26,9): error TS2322: Type 'Point3DImpl' is not assignable to type 'Point3D'.

Impressive! So there seems to be something wrong in my inheritance hierarchy. I start from top, trying to fix the first error.

The BoxImpl.ts message "Types of property 'distance' are incompatible" tells me that function signatures contradict each other. I can not overload function distance(other: Point) in Point.ts with distance(other: Point3D). Same is with transform(other: Point).

So I will fix this like the following: Point3D will get the more specific function names distance3D() and transform3D().

Point3D.ts
import { Point } from "../Point.js";

export interface Point3D extends Point
{
    readonly z: number;
    distance3D(): number;
    distance3D(other: Point3D): number;
    transform3D(origin: Point3D): Point3D;
}
Point3DImpl.ts
import { Point3D } from "./Point3D.js";
import { PointImpl } from "../PointImpl.js";

export class Point3DImpl extends PointImpl implements Point3D
{
    ....
 
    distance3D(other?: Point3D): number    {
        .... // code not changed!
    }
    
    transform3D(origin: Point3D): Point3D    {
        .... // code not changed!
    }

    ....
}

That was it. After doing these changes the compiler did not report any more errors. So all the messages above were follower problems due to incompatible function overloading!

Conclusion

Sharpening compiler options may require a little patience. But a lot of tsc errors seem to be followers of others. Don't be afraid of big error lists. Strict typing is always better than sitting for hours on debugging hard to reproduce bugs.




Donnerstag, 3. Mai 2018

TypeScript Interfaces are Open Ended

On the test bench is TypeScript with "Interfaces are Open Ended". This is about following capability:

interface Point
{
    x: number;
    y: number;
}

const myPoint: Point = {
    x: 1,
    y: 2
};

interface Point {
    z: number;
}

If you compile this, you get:

error TS2322: Type '{ x: number; y: number; }' is not assignable to type 'Point'.
  Property 'z' is missing in type '{ x: number; y: number; }'.

It looks like TypeScript reads interfaces first. It collects all members that it finds for Point into just one interface (→ "open ended"). Then it reads the remaining code and finds that myPoint violates the interface Point contract, because the property z is not present.

In other words, it regards the duplicate definition of Point as feature, not as bug. The "premature" definition of myPoint is regarded to be the bug.

I don't want to argue about bugs and features. Here is how I would fix this:

interface Point
{
    x: number;
    y: number;
}

interface Point3D extends Point
{
    z: number;
}

const myPoint: Point = {
    x: 1,
    y: 2
};

You should define any interface just once. When you need more, extend it, but with a new descriptive name (like Point3D). That's the OO way, still the most popular and successful one. Do not use language features that are useless. But you should also read some TS docs (see "Modifying native types") and decide with your team if "open ends" are acceptable.

It is relieving to find out that such doesn't work when you have this second interface Point in another file. Compiling following sources

interface-extensions.ts
....
export interface Point
{
    z: number;
}
....
Point.ts
....
import { Point } from "./interface-extensions.js";
....

interface Point
{
    x: number;
    y: number;
}

const myPoint: Point = {
    x: 1,
    y: 2
};

gives you this error:

error TS2440: Import declaration conflicts with local declaration of 'Point'.

That's what I'd expected!

Freedom versus Safety

Why do programming languages provide freedom of expression? When it is about realizing technical issues for machines that do not forgive any mistake, freedom is not what we need. We need simple and precise expressions, built from culturally rooted terms. We need to be able to communicate about written source code, because very often the source code is the only existing business specification.

A single person can not cope with the complexity of today's applications, this must be done collectively. For that we need a common and easily understandable programming language. Freedom of expression makes communication about implemented functionality difficult up to impossible due to unreadable code.

So why are all these new languages full of freedom features that nobody needs? Because the language researchers need customers. They do everything to get you on their side. It's the free play of forces.




Mittwoch, 2. Mai 2018

TypeScript Index Signatures

This Blog is about the attempt of TypeScript to keep developers from "shooting themselves in their feet".

1
2
3
4
5
interface Foot
{
    owner: string;
    [shot: string]: string;
}

The index signature is in line 4.

All examples here have been compiled with default compiler settings by

tsc *.ts

Maybe the results of my findings about index signatures can be modified by some compiler settings. In that case I will write as much versions of this Blog as settings exist :-)

The Shot in the Foot

JavaScript objects are maps, and maps are objects, I will use these terms synonymously in the following (although real Maps found their way to us via ES6). So this is about var object = {}.

JavaScript lets access objects by square brackets, e.g. object[something], whereby the key something will always by stringified before going into the map, no matter of what type it is. In other words, the key's toString() method will be called, and only the resulting string will be used as map-key. This can be quite misleading. Look at following JavaScript example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const foot = {};

const stringShot = "1";
const stringVictim = "String-indexed";

const numericShot = 1;
const numericVictim = "Numeric-indexed";

const objectShot = { toString: function() { return "1"; } };
const objectVictim = "Object-indexed";

foot[stringShot] = stringVictim;
foot[numericShot] = numericVictim;
foot[objectShot] = objectVictim;

console.assert(
    foot[stringShot] === stringVictim,
    "Value of string key value must not be overwritten!");
console.assert(
    foot[numericShot] === numericVictim,
    "Value of numeric key value must not be overwritten!");
console.assert(
    foot[objectShot] === objectVictim,
    "Value of object key value must not be overwritten!");

This is syntactically valid JavaScript. It defines three different key-value pairs, of type string, number and object. Then they get shot into foot. The numeric 1, the string "1", the object with "1" from toString(), all keys are put to the same map location, and thus overwrite each other. Just the last assert() will be successful, the first and the second will fail.

The TS Prevention

Compilíng this with TypeScript gives:

error TS2538: Type '{ toString: () => string; }' cannot be used as an index type.

This error refers to variable objectShot. That means, TS refuses to let index with an object-key, but still allows both number and string. Thus the number of possible shots in the foot was reduced to confusing number and string keys.

So will "Index Signatures" protect our feet?

About Index Signatures

TS calls the square bracket object access "indexing", and introduces the new term "index signature", also called "indexable type".

Here is how a TS programmer would write the JS example above. We learnt that TS refuses to let index by object, so the objectShot was removed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const foot: { [shot: string]: string } = {};

const stringShot = "1";
const stringVictim = "String-indexed";

const numericShot = 1;
const numericVictim = "Numeric-indexed";

foot[stringShot] = stringVictim;
foot[numericShot] = numericVictim;

console.assert(
    foot[stringShot] === stringVictim,
    "Value of string key value must not be overwritten!");
console.assert(
    foot[numericShot] === numericVictim,
    "Value of numeric key value must not be overwritten!");

The first line types foot to be an object that can be indexed by a string key, and can contain just strings. At least the code looks like this. In fact also number keys can be used without compile error, as we see in the subsequent code, where foot[numericShot] = numericVictim is done. This example is syntactically valid TS.

Just the second assert() succeeds, the first fails, because still the numeric 1 overwrites the string "1". Even when I change the index signature to

const foot: { [shot: number]: string } = {};

it would make no difference. The compiler doesn't care as long as the key is string or number.

So what is an index signature for, when it doesn't restrict the key to just one type?
It is the value type that gets restricted.

const foot: { [shot: string]: string } = {};

const numericShot = 1;
const numericVictim = 12345;

foot[numericShot] = numericVictim;

Compiling this with tsc gives

error TS2322: Type '12345' is not assignable to type 'string'.

because the index signature allows only strings as indexed values.
By the way, an indexed object can contain just objects of the data type defined by the index signature, in this case strings!

const foot: {
    length: number,
    [shot: string]: string
} = {
    length: 0
};

Compiling this gives:

error TS2322: Type '{ length: number; }' is not assignable to type '{ [shot: string]: string; length: number; }'.
  Property 'length' is incompatible with index signature.
    Type 'number' is not assignable to type 'string'.
error TS2411: Property 'length' of type 'number' is not assignable to string index type 'string'.

A Type Having Properties and Being Indexable

The final application that I find for index signatures is having a data type that can be accessed by properties (object.propertyName) and indexed by numeric or string keys (object[key]). Hm, isn't this like in JavaScript? The difference is that here all values can be just of one data type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
interface Foot
{
    owner: string;
    [shot: string]: string;
}

const foot: Foot = {
    owner: "TheOwner"
};
foot["1"] = "String-indexed";
foot[1] = "Numeric-indexed";

console.assert(
    foot.owner === "TheOwner",
    "Value of owner must be 'TheOwner'!");
console.assert(
    foot["1"] === "String-indexed",
    "Value of '1' must be 'String-indexed'!");
console.assert(
    foot[1] === "Numeric-indexed",
    "Value of 1 must be 'Numeric-indexed'!");

Here the index signature is used inside an interface Foot. The subsequent concrete foot implements that interface and puts a default value into the owner property. Then it indexes the object, once via string, once via number.

Unfortunately the numeric key erases the string key again. The first assert succeeds, because it goes to the named property. The second assert goes wrong, because the string "1" has been overwritten by the numeric 1. The third succeeds, because it asserts the "last shot".


Conclusion

That's how I understand index signatures:

  • restrict the value-type of named and indexed properties

In other words, if you restrict the value-type by an index signature, you can't have named properties of other types. What you obviously want, why else would you have created an object? If you want to index something: arrays and maps (→ real ES6 Maps) specialize on this!

Restrict the key-type to string and number? No, this would be checked even without an index signature!

  • Update: with compiler-switch "noImplicitAny": true, you can actually restrict the type of the key to just one type!

Index signatures do not enable indexing. This works anyway. Some say index signatures can loosen typing:

interface Foot
{  
    shot: string;  
    [excessProperty: string]: any; // allow additional properties of any type
}

Here the index signature is used to enable properties of any type within an object implementing the interface Foot.
So, we loosened typing. But why do we use TypeScript instead of JavaScript? Was it because we wanted the compiler to type-check and tell us about misplaced properties? Or was it because we wanted to continue shooting in our feet?

Last not least I plead for collecting all weapons and destroying them. Maybe this prevents shots in the feet :-?




Dienstag, 1. Mai 2018

Getting Used to TypeScript by Testing

In this Blog I will describe how I tested my recent TypeScript implementations.

My problem is that I want to stick to EcmaScript 6 import statements, but nodejs seems to not support them. So I decided to create a small test framework based on the browser's JS engine. At least the web-browser is the final goal of all TypeScript or JavaScript code.

Infrastructure

This is not an infrastructure for professional TypeScript projects!

I have a number of source directories, nested into each other. In case you compile with tsc -t ES6, the TS source files there do not see each other unless you use import statements, even when they are in the same directory. Following outlines one of these directories, it is the one that contains the sources for Point, Dimension, Rectangle and their implementation classes. These are the targets of my test.

ts-examples
test.html
interfaces
geometry
Dimension.ts
DimensionImpl.ts
Point.ts
PointImpl.ts
Rectangle.ts
RectangleImpl.ts
test.ts

The page test.html is my test page, and interfaces/geometry/test.ts is the TypeScript test I want to execute. Because test.html is above all of my source directories, I can run many test.ts with it, I just need to write their relative paths into test.html. See below how this works.

Before I run the test by loading test.html into an ES6-able web-browser, I need to compile my TS sources with:

cd ts-examples/interfaces/geometry
tsc -t ES6 *.ts

This leaves an XXX.js file for each XXX.ts file in the same directory.

Test Page HTML

Here comes the test.html page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"/>
        <title>TypeScript Test Page</title>
    </head>

    <body>
        <div style="position: fixed; left: 4px; right: 4px; background-color: lightGray; padding: 4px;">
            Number of tests: <span id="numberOfTests">0</span>, fails: <span id="numberOfFails">0</span>
        </div>
        
        <div id="logPanel" style="padding-top: 2em;"></div>
        
        <script>
            var increment = function(elementId) {
                var number = document.getElementById(elementId);
                number.innerHTML = ""+(parseInt(number.innerHTML) + 1);
            };
            
            var logPanel = document.getElementById("logPanel");
            var consoleLog = console.log;
            
            console.log = function(output, color, isTitle) {
                if ( ! isTitle && consoleLog )
                    consoleLog(output);
                    
                var line = document.createElement(isTitle ? "H2" : "DIV");
                line.style.color = (color !== undefined) ? color : "green";
                line.innerHTML = output;
                logPanel.appendChild(line);
                
                if ( isTitle )
                    return;
                    
                increment("numberOfTests");
                if (color === "red")
                    increment("numberOfFails");
            };
            
            window.addEventListener("error", function(error) {
                if ( ! error.error )
                    return;
                    
                console.log(error.error, "magenta");
            });
        </script>
        
        <script>
            var title = function(testTitle) {
                console.log(testTitle, "blue", true);
            };
            
            var assert = function(criterion, message) {
                if (criterion)
                    console.log(message);
                else
                    console.log(message, "red");
            };
        </script>

        
        <!-- Tests to display on this page -->
        
        <script type="module" src="interfaces/geometry/test.js"></script>
        <!-- <script type="module" src="interfaces/indexable-types/test.js"></script> -->
        <!-- <script type="module" src="interfaces/interfaces-are-open-ended/test.js"></script> -->
        
    </body>

</html>

Line 9 - 11 represent a fixed top-bar that shows how many tests were executed, and how many of them failed.

Line 13 contains the output panel that shows all titles and messages from the executed tests. In case the test fails, the message is displayed in red, in case of success green.

Line 15 - 47 hold a JavaScript that manages these outputs.
The increment() function will read a number from an element, increment it, and store it back to the element. To get all outputs into the page, the console.log function is overridden by a function that writes into the HTML page. It allows three parameters, additionally to the output text also a color for the output, and a flag whether the output is a test-title. This anonymous function writes into the log area and increments the counters on top.
Finally a page error handler is installed via window.addEventListener("error", ....), this redirects any possible JS interpreter runtime exception also to the page, the color would be magenta.

Line 49 - 60 provide two utility functions for tests: The ubiquitous assert(), and a possibility for the test to state its title(). The color of a title will be blue, an error will be red, any other message will have the default color green. These two functions must be declared by any test.js file. See below how to do that.

Line 65 - 67 contain the test scripts to execute. Currently just one is commented-in, so we will see just one title.

Test Script TS

Finally here is the TS code that tests the sources in same directory. Mind that in a real project you would try to separate tests from production code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { Point } from "./Point.js";
import { PointImpl } from "./PointImpl.js";
import { Dimension } from "./Dimension.js";
import { DimensionImpl } from "./DimensionImpl.js";
import { Rectangle } from "./Rectangle.js";
import { RectangleImpl } from "./RectangleImpl.js";

declare function title(assertTitle: string): void;
declare function assert(criterion: boolean, message: string): void;

title("Geometry");

// Point

const point: Point = new PointImpl(3, 4);
assert(
    point.distance() === 5,
    "Point(3, 4) distance from origin(0, 0) must be 5!");
    
assert(
    point.distance(new PointImpl(-3, -4)) === 10, 
    "Point(3, 4) distance from point(-3, -4) must be 10!");
    
const transformedPoint: Point = point.transform(new PointImpl(7, 10));
assert(
    transformedPoint.x === -4 && transformedPoint.y === -6,
    "Point(3, 4) transformation to origin(7, 10) must be (-4, -6)!");
    
// Dimension

const dimension: Dimension = new DimensionImpl(2, 3);
assert(
    dimension.area() === 6,
    "Dimension(2, 3) area must be 6!");

// Rectangle

const rectangle: Rectangle = new RectangleImpl(point, dimension);
assert(
    rectangle.x === point.x && point.x === 3 && rectangle.y === point.y && point.y === 4,
    "Rectangle location must be ("+point.x+", "+point.y+")!");
assert(
    rectangle.width === dimension.width && rectangle.height === dimension.height,
    "Rectangle dimension must be ("+dimension.width+", "+dimension.height+")!");
assert(
    rectangle.area() === dimension.area(),
    "Rectangle area must be "+dimension.area()+"!");

const movedRectangle = rectangle.move(transformedPoint);
assert(
    movedRectangle.x === transformedPoint.x && movedRectangle.y === transformedPoint.y,
    "Rectangle must have moved to ("+transformedPoint.x+", "+transformedPoint.y+")!");
    
const newDimension: Dimension = new DimensionImpl(4, 7);
assert(
    rectangle.resize(newDimension).area() === 28,
    "Rectangle must have been resized to an area of 28!");

//rectangle.width = 999; // compile error!

Unlike in Java there is no heading package statement in TS. The source starts with importing classes and interfaces it wants to test.

Line 8 - 9 contain the declaration of external functions that test.html will provide. Without this the compiler would report an error.

The TS declare keyword is like the C extern, or the Java abstract, it lets the compiler know that there will be something in place at runtime, and it defines the type of it, so that the compilation does not fail.

Line 11 outputs the title of the test, to be seen in blue.

The remaining lines contain calls to objects of the imported classes, and subsequent assertions about the result of these calls. Each assert() call will generate a red or green line in the HTML page.

The out-commented statement in Line 59 tries to set a value into the readonly property width. I had to comment it out because the compiler reported an error. If you would put it into the compiled test.js, it would work, the property would actually accept the value. All these checks that TS provides exist just at compile time!

This is a screenshot of test.html after loading it via a file-URL. (I tweaked one of the tests to fail for trying out if it would be displayed red.)

Conclusion

This Blog showed how TS can be tested directly in a browser, using just one HTML-page with a small JS framework.

After having spent a lot of time trying to make nodejs execute my compile results that use ES6 import statements, I am glad that I can test my TS code in that easy way now.