cli  
Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

Preface

Goal: A small step into typescript using custom song script example.

I’m not always writing for beginner. Most of the time, I’m a beginner myself. And all I want is just to share, from beginner to beginner. That is why I never write something tagged as for expert, or even intermediate stuff.

This is yet, just another ordinary mediocre article, that you can skip.

Why Typescript?

Don’t be afraid to try something new.

I know it s better to master one or two things, than just know all jacks, but master of none. But the thing is beginner should broader their knowledge, as wide as possible, so they don’t get scared, of any language, framework or whatsoever, because they have already know al littel about it. This means when you have a challenge, or even a profitable job, you can get there more quickly.

Broader your view!

So the answer is I don’t know why typescript. I choose to learn many stuff, including this typescript. This is the real reason.

Source Examples

You can obtain source examples here:

Official Documentation


The Original Script

Edge of Language: Code, Error, Repeat.

Technically, coder write typescript, and compile it to javascript. But in real world, beginner start using javascript instead. Then try to convert the javascript into typescript, gradually learn from the simple script.

We need a proper example. Something that simple enough to get started with. Not too complex, but not too short either.

The Example

Because I love singing.

Again, why this sort of song script example? Over and over again.

Actually I have other bunch of script that can be used as an example. I just think that data structure, is right to learn a new language. Then the next step would be parameter argument in 16 different languages, but I don’t have time to write all this these days. So I stick with this simple song script example.

Songs

What could gone wrong with this perfectly working ecmascript?

At a glance, the complete script is as below.

const songs  = [
  { title: "Cantaloupe Island",          tags: ["60s", "jazz"] },
  { title: "Let it Be",                  tags: ["60s", "rock"] },
  { title: "Knockin' on Heaven's Door",  tags: ["70s", "rock"] },
  { title: "Emotion",                    tags: ["70s", "pop"] },
  { title: "The River" }
];

let tagSet = new Set();

// using set feature to collect tag names
songs.forEach(function(song) {
  if( "tags" in song ) {
    let tags = song.tags;
    console.log(tags);

    for (const tag of tags) {
      tagSet.add(tag);
    }
  }
});

console.log(tagSet);

// normalize to array
let alltags = [...tagSet];

console.log(alltags);

Prepare

Luclily all the node package can be installed globally.

Install The Compiler

This is all the typescript is.

$ npm i -s -g typescript
…
+ typescript@4.1.2
updated 1 package in 6.35s

Typescript: NPM Install: The Compiler

Install TS Node

We need this package to run from the node directly.

$ npm i -s -g ts-node
…
+ ts-node@9.1.1
updated 1 package in 4.123s

Typescript: NPM Install: TS Node

TS Config

When using ts-node we can specify the target in tsconfig.json. For example using ES2015 as below code.

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Recommended"
}

1: First Interface

One small step.

Consider giving interface for a very simple structure.

Original Ecmascript

Just oneliner data structure.

const song: Song = { title: "Cantaloupe Island", tags: ["60s", "jazz"] };

console.log(song);

Rewritten in Typescript

We need to consider that tags is optional, then we use tags? notation.

interface Song{
  title: string;
  tags?: Array<string>;
}

const song: Song = { title: "Cantaloupe Island", tags: ["60s", "jazz"] };

console.log(song);

Compile, View And Run

There are a few steps:

$ cd ~/Documents/songs/typescript

We can specified the target directly in command. Or set the target in tsconfig.json.

$ tsc 01-song.ts --target es6

If everything works well, we can check the javascript result, generated by the compiler.

$ cat 01-song.js
const song = { title: "Cantaloupe Island", tags: ["60s", "jazz"] };
console.log(song);

And also test in node, if the compilation works well.

$ node 01-song.js
{ title: 'Cantaloupe Island', tags: [ '60s', 'jazz' ] }

Typescript: Compile, View And Run

This is all in the nutshell.

Just Run

If you don’t like those mumbo jumbo above, you can directly run as an ordinary script.

$ ts-node 01-song.ts
{ title: 'Cantaloupe Island', tags: [ '60s', 'jazz' ] }

Typescript: Just run with ts-node

So far so good. But beware of trouble ahead.


2: Record Declaration

Giving The Right Type

I was confused about what type suitable for the new Set(). Until I found out it was only Set<>, just like Array<>.

interface Song{
  title: string;
  tags?: Array<string>;
}

const songs: Array<Song>  = [
  { title: "Cantaloupe Island",          tags: ["60s", "jazz"] },
  { title: "Let it Be",                  tags: ["60s", "rock"] },
  { title: "Knockin' on Heaven's Door",  tags: ["70s", "rock"] },
  { title: "Emotion",                    tags: ["70s", "pop"] },
  { title: "The River" }
];

let tagSet: Set<string> = new Set();

Compile

Giving rest of the code intact, the code should compiled successfully.

$ tsc songs.ts --target es6
$ node songs.js
[ '60s', 'jazz' ]
[ '60s', 'rock' ]
[ '70s', 'rock' ]
[ '70s', 'pop' ]
Set(5) { '60s', 'jazz', 'rock', '70s', 'pop' }
[ '60s', 'jazz', 'rock', '70s', 'pop' ]

Happy 🙂? Don’t be so sure 😀.

Run

The ts-node might say something different.

 ts-node 01-songs.ts

/home/epsi/.npm-global/lib/node_modules/ts-node/src/index.ts:513
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError:  Unable to compile TypeScript:
songs.ts:22:23 - error TS2532: Object is possibly 'undefined'.

22     for (const tag of tags) {
                         ~~~~

Typescript: ts-node: possibly undefined

Woat 😳!

TS Playground

Debugging

The code is shown as below. What could possibly wrong?

songs.forEach(function(song) {
  if( "tags" in song ) {
    let tags = song.tags;
    console.log(tags);

    for (const tag of tags) {
      tagSet.add(tag);
    }
  }
});

After a few of stackoverflowing, I read that people suggest to play with TS playground.

This does not solve my issue directly. But somehow, I can find the problem faster.

Since this online site give live result, and also live error messages. I can write and remove code quickly. Until i finally found this line is messed up.

  if( "tags" in song ) {   }

This can be solved by changing into:

  if( song.tags ) {...}

Now the code works well again.

Finally

Now I can peacefully finish this imperative code.

// using set feature to collect tag names
songs.forEach(song => {
  if( song.tags ) {
    let tags: Array<string> = song.tags;
    console.log(tags);

    for (const tag of tags) {
      tagSet.add(tag);
    }
  }
});

console.log(tagSet);

// normalize to array
let alltags: Array<string> = [...tagSet];

console.log(alltags);

With the result as below:

 ts-node 01-songs.ts
[ '60s', 'jazz' ]
[ '60s', 'rock' ]
[ '70s', 'rock' ]
[ '70s', 'pop' ]
Set(5) { '60s', 'jazz', 'rock', '70s', 'pop' }
[ '60s', 'jazz', 'rock', '70s', 'pop' ]

Typescript: ts-node: succeed

This typescript code run well. But don’t stop, there is other improvement to come, that can be attached to this very script.


3: Array Interface

How do I write interface for array 🤔?

Basic Syntax

This code works well.

interface StringArray {
  [index: number]: string;
}

const tags: StringArray = ["rock", "jazz", "rock", "pop", "pop"];
console.log(tags);

With the result as below:

$ ts-node 02-tags-set.ts
[ 'rock', 'jazz', 'rock', 'pop', 'pop' ]

What could be wrong then 🤔?

The Iterator

Consider run it in a loop.

interface StringArray {
  [index: number]: string;
}

const tags: StringArray = ["rock", "jazz", "rock", "pop", "pop"];

// using set feature to collect tag names
let tagSet: Set<string>  = new Set();
for (const tag of tags) { tagSet.add(tag); }
console.log(tagSet);

You will see an error message as below:

TSError:  Unable to compile TypeScript:
02-tags-set.ts:9:19 - error TS2488: Type 'StringArray' must have a '[Symbol.iterator]()' method that returns an iterator.

9 for (const tag of tags) { tagSet.add(tag); }

To solve this, you just need to add symbol in the interface.

interface StringArray {
  [index: number]: string;
  [Symbol.iterator]() : IterableIterator<string>;
}

At last, I can calm myself down, by completing this imperative code.

interface StringArray {
  [index: number]: string;
  [Symbol.iterator]() : IterableIterator<string>;
}

const tags: StringArray = ["rock", "jazz", "rock", "pop", "pop"];

// using set feature to collect tag names
let tagSet: Set<string>  = new Set();
for (const tag of tags) { tagSet.add(tag); }
console.log(tagSet);

// normalize to array
const allTags: StringArray = [...tagSet];
console.log(allTags);

With the code result as below array:

 ts-node 02-tags-set.ts
Set(3) { 'rock', 'jazz', 'pop' }
[ 'rock', 'jazz', 'pop' ]

Function Expression

How about functional approach 🤔? Of course we can rewrite above code using oneliner functions.

interface StringArray {
  [index: number]: string;
  [Symbol.iterator]() : IterableIterator<string>;
}

const unique = (array: StringArray): StringArray => [... new Set(array)];
const tags: StringArray = ["rock", "jazz", "rock", "pop", "pop"];

const allTags: StringArray = unique(tags);
console.log(allTags);

With the code result as below array:

$ ts-node 03-tags-unique.ts
[ 'rock', 'jazz', 'pop' ]

Typescript: Unique Tags


4: Which Implementation?

There are different way to achieve the same result. What best is depend on the use case.

Consider this three different implementation, to handle the same one song record.

Using Basic Type Directly

interface Song{
  title: string;
  tags?: Array<string>;
}

const unique = (array: Array<string>): Array<string> => [... new Set(array)];

const song: Song = {
  title: "Cantaloupe Island", 
  tags: ["rock", "jazz", "rock", "pop", "pop"]
};

const songTags: Array<string> = unique(song.tags!);
console.log(songTags);
$ ts-node 04a-song-test.ts
[ 'rock', 'jazz', 'pop' ]

Using Interface

interface StringArray {
  [index: number]: string;
  [Symbol.iterator]() : IterableIterator<string>;
}

interface Song{
  title: string;
  tags?: StringArray;
}

const unique = (array: StringArray): StringArray => [... new Set(array)];

const song: Song = {
  title: "Cantaloupe Island", 
  tags: ["rock", "jazz", "rock", "pop", "pop"]
};

const songTags: StringArray = unique(song.tags!);
console.log(songTags);

With about the same output result.

Using Type Reserved Word (Type Synonym)

type StringArray = Array<string>;

interface Song{
  title: string;
  tags?: StringArray;
}

const unique = (array: StringArray): StringArray => [... new Set(array)];

const song: Song = {
  title: "Cantaloupe Island", 
  tags: ["rock", "jazz", "rock", "pop", "pop"]
};

const songTags: StringArray = unique(song.tags!);
console.log(songTags);

With about the same output result.

Behaviour

But beware of this typescript behaviour:

interface StringArray {
  [index: number]: string;
  [Symbol.iterator]() : IterableIterator<string>;
}

interface Song{
  title: string;
  tags?: Array<string>;
}

const unique = (array: Array<string>): StringArray => [... new Set(array)];

const song: Song = {
  title: "Cantaloupe Island", 
  tags: ["rock", "jazz", "rock", "pop", "pop"]
};

const songTags: Array<string> = unique(song.tags!);
console.log(songTags);

This will result an error. Because StringArray and Array<string>) is not synoymous.


What is Next 🤔?

We are still far from finished. We need to conclude.

Consider continue reading [ Typescript - Small Step into Typed - Part One ].