2021/09/08

TypeScript入門 第6回 モジュール定義

これまで, ソースコードはindex.tsファイルに書いてきました. プログラムもまとまってきましたので, ソースファイルを管理しやすくするために, ファイルを分割してモジュールを定義します.

1)ソースファイルを分割する

srcディレクトリにrpn.tsファイルを作り, index.tsファイルのほぼすべての内容, データ型の定義と関数定義をコピーします. index.tsファイルに関数定義を残したままにしておくと, コンパイラがエラーを報告します.


rpn.tsファイルのparse関数とcalc関数にexportを付けるとエラーが解消します. 


index.tsから関数定義を削除すると, 関数が見つからないとコンパイラはエラーを報告します.

この結果から分かったことは次の通りです.

  1. 新しく作ったrpn.tsファイルにindex.tsから関数をコピーしただけでは, rpn.tsファイルの内容はindex.tsと同じモジュールにあるものとして扱う.
  2. rpn.tsファイルで公開する関数にexportを付けるとrpn.tsファイルの内容を別のモジュールとして扱う.

TypeScriptの仕様を確認したわけではないので, 間違えていたらスミマセン.

2)モジュールから関数をインポートする

関数をインポートしてみます. 

import {parse, calc} from './rpn'

let input = process.argv.slice(2, process.argv.length)
// 計算してみる
console.log('解析結果', parse(input))
console.log('計算結果', calc(parse(input)))

1行目のimport文を追加すると, コンパイルエラーは解消しました. この方法であれば, index.tsを修正せずに済みますが, rpn.tsファイルで定義してある関数であることを明示する方が私の好みです.

import * as rpn from './rpn'

let input = process.argv.slice(2, process.argv.length)
// 計算してみる
console.log('解析結果', rpn.parse(input))
console.log('計算結果', rpn.calc(rpn.parse(input)))

3)まとめ

この記事では, ソースファイルを分割してモジュールを定義する方法を調べました. ただし, この記事の内容はコンパイラの応答から推測した結果をまとめただけであり, 言語仕様などを確認する必要があります. また, 実行環境等によって違いがあるようなので注意が必要です. 

最後に, この記事で使用したNode.jsとTypeScriptのバージョンは次の通りです.

PS C:\TSWork\Module> node --version
v14.17.5
PS C:\TSWork\Module> .\node_modules\.bin\tsc --version
Version 4.4.2
PS C:\TSWork\Module>

2021/09/04

TypeScript入門 第5回 データ型定義

前回, コマンドライン引数から逆ポーランド記法で記述した式を入力できるようにしました. 入力のチェックを何もしていないので, 対策していきます. まずは状況の確認から始めます.

1)エラーが生じる条件

プログラムには想定している入力の範囲があります. 作成しているプログラムでは, 次のような入力を想定しています.

  1. 数値
  2. 四則演算子(+, -, ×, /)

想定していない誤った式を入力してみます.

PS C:\TSWork\RPN> node .\dist\index.js 9 A + 4 *
計算結果 NaN

9にAを加えていますが, AをNaNと解釈したため, 計算結果もNaNとなっています. もう一つ試してみます.

PS C:\TSWork\RPN> node .\dist\index.js 9 3 * 4 %
C:\TSWork\RPN\dist\index.js:39
        throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?');
        ^

Error: スタックの要素数が1ではありません. 式を間違えているかも?
    at calc (C:\TSWork\RPN\dist\index.js:39:15)
    at Object.<anonymous> (C:\TSWork\RPN\dist\index.js:44:21)
[90m    at Module._compile (internal/modules/cjs/loader.js:1072:14)[39m
[90m    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)[39m
[90m    at Module.load (internal/modules/cjs/loader.js:937:32)[39m
[90m    at Function.Module._load (internal/modules/cjs/loader.js:778:12)[39m
[90m    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)[39m
[90m    at internal/main/run_main_module.js:17:47[39m
PS C:\TSWork\RPN>

余りを求める演算子のつもりで「%」を使っています. 「%」もNaNと解釈しているため, このようなエラーを表示しています. 最低限の対策として, 文字列をNaNと解釈した場合にエラーを投げるようにすれば良さそうです.

2)エラー処理の方針

このまま対策を入れてしまうと, 逆ポーランド記法で記述した式を計算するcalc関数が複雑になってきます. そこで, エラー処理を2つに分けます.

  1. 入力した文字列が想定している範囲内にあることを確認する。
  2. 式の構造が正しいことを確認する。

式の構造の正しさは, calc関数で計算を進めながら確認していきます. この確認は, 既にcalc関数に作ってあります. 足りないことは, 入力文字列が想定内であることを確認する処理です. これを作っていきます.

3)データ型の定義

入力文字列をチェックする関数など簡単に作成できますが, もう少し考えを進めます. この程度のプログラムではありえないのですが, 2人のプログラマが関わっており, 入力をチェックする部分と, 計算する部分に担当が分かれているとします. 2人の間でうまく連携を取らないと, 入力のチェック後に計算担当へ文字列配列を渡した後で, 計算の担当者が受け取った文字列配列をチェックし始めます. 

二重にチェックをしてしまうと, 仕様変更時の修正箇所が増えてしまい, 修正漏れがあると不具合の原因となります. そこで, 2人のプログラマの間での取り決めとして, データ型の配列を渡すようにします. データ型を定義して, プログラムを修正した結果は次の通りです.

// 数式を構成する要素に対応するデータ型の定義
type Token = 
        | 'ADD'   // 足し算
        | 'SUB'   // 引き算
        | 'MUL'   // 掛け算
        | 'DIV'   // 割り算
        | number  // 数値

// 文字列からTokenを作る
function makeToken(str: string) {
    let token: Token
    switch (str) {
        case '+': token = 'ADD'; break
        case '-': token = 'SUB'; break
        case '*': token = 'MUL'; break
        case '/': token = 'DIV'; break
        default:
            let n = parseFloat(str)
            if (isNaN(n)) {
                throw new Error(`対応していない入力です:${str}`)
            } else {
                token = n 
            }
    }
    return token
}

// 文字列の配列をTokenの配列に変換する.
function parse(inputArray: string[]) {
    let result: Token[] = []
    for (let value of inputArray) {
        result.push(makeToken(value))
    }
    return result
}

// 補助関数
function calcAux(stack: number[], op: (a: number, b: number) => number) {
    let b = stack.pop()
    let a = stack.pop()
    if ('number' === typeof a && 'number' === typeof b) {
        stack.push(op(a, b))
    } else {
        throw new Error('数字が入ってないよ')
    }
}

// 配列を逆ポーランド記法で記述した式として計算する.
function calc(input: Token[]) {
    let stack: number[] = []
    for (let token of input) {
        switch (token) {
            case 'ADD' : calcAux(stack, (a, b) => a + b); break
            case 'SUB' : calcAux(stack, (a, b) => a - b); break
            case 'MUL' : calcAux(stack, (a, b) => a * b); break
            case 'DIV' : calcAux(stack, (a, b) => a / b); break
            default : stack.push(token)
        }
    }

    if (1 === stack.length) {
        return stack.pop()
    } else {
        throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?')
    }
}

let input = process.argv.slice(2, process.argv.length)
// 計算してみる
console.log('解析結果', parse(input))
console.log('計算結果', calc(parse(input)))

修正した内容を説明します. まず, データ型の定義から.

// 数式を構成する要素に対応するデータ型の定義
type Token = 
        | 'ADD'   // 足し算
        | 'SUB'   // 引き算
        | 'MUL'   // 掛け算
        | 'DIV'   // 割り算
        | number  // 数値

式を構成する演算子や数値を表すデータ型を定義します. 演算子に対応した文字列リテラルとnumberの合併としています. これだけ見ると, わざわざ型を導入する必要があるのか, と自分でも疑問を感じますが, 後でありがたみが分かってきます.

// 文字列からTokenを作る
function makeToken(str: string) {
    let token: Token
    switch (str) {
        case '+': token = 'ADD'; break
        case '-': token = 'SUB'; break
        case '*': token = 'MUL'; break
        case '/': token = 'DIV'; break
        default:
            let n = parseFloat(str)
            if (isNaN(n)) {
                throw new Error(`対応していない入力です:${str}`)
            } else {
                token = n 
            }
    }
    return token
}

文字列をToken型に変換するための関数を定義しました. 文字列の配列のままならこの関数も不要なので, Token型を作らなければ良かったのでは, と後悔し始めています.

// 文字列の配列をTokenの配列に変換する.
function parse(inputArray: string[]) {
    let result: Token[] = []
    for (let value of inputArray) {
        result.push(makeToken(value))
    }
    return result
}

文字列の配列をTokenの配列に変換するための関数も定義しました. Token型を導入したばかりに, コード量が2倍に膨れ上がりました. 失敗か?

// 配列を逆ポーランド記法で記述した式として計算する.
function calc(input: Token[]) {
    let stack: number[] = []
    for (let token of input) {
        switch (token) {
            case 'ADD' : calcAux(stack, (a, b) => a + b); break
            case 'SUB' : calcAux(stack, (a, b) => a - b); break
            case 'MUL' : calcAux(stack, (a, b) => a * b); break
            case 'DIV' : calcAux(stack, (a, b) => a / b); break
            default : stack.push(token)
        }
    }

    if (1 === stack.length) {
        return stack.pop()
    } else {
        throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?')
    }
}

逆ポーランド記法で記述した式を計算するための関数本体です. 引数inputの型を文字列の配列からTokenの配列に変更しています. Token型に変更したので, ifからswitchに変更しました. スッキリとしました. 

let input = process.argv.slice(2, process.argv.length)
// 計算してみる
console.log('解析結果', parse(input))
console.log('計算結果', calc(parse(input)))

最後に, コマンドライン引数をcalc関数に渡すため, parse関数を間に挟んでいます. コンパイルして実行した結果を確認します. 

PS C:\TSWork\RPN> .\node_modules\.bin\tsc
PS C:\TSWork\RPN> node .\dist\index.js 9 4 * 6 /
解析結果 [ 9, 4, 'MUL', 6, 'DIV' ]
計算結果 6
PS C:\TSWork\RPN> node .\dist\index.js 9 4 * 6 %
C:\TSWork\RPN\dist\index.js:21
                throw new Error("\u5BFE\u5FDC\u3057\u3066\u3044\u306A\u3044\u5165\u529B\u3067\u3059:" + str);
                ^
Error: 対応していない入力です:%
    at makeToken (C:\TSWork\RPN\dist\index.js:21:23)
    at parse (C:\TSWork\RPN\dist\index.js:34:21)
    at Object.<anonymous> (C:\TSWork\RPN\dist\index.js:79:21)
[90m    at Module._compile (internal/modules/cjs/loader.js:1072:14)[39m
[90m    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)[39m
[90m    at Module.load (internal/modules/cjs/loader.js:937:32)[39m
[90m    at Function.Module._load (internal/modules/cjs/loader.js:778:12)[39m
[90m    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)[39m
[90m    at internal/main/run_main_module.js:17:47[39m
PS C:\TSWork\RPN>
エラーをキャッチしていないのはどうかと思いますが, 入力に応じた計算ができています.

4)型システムの恩恵

Token型を導入したありがたみを確認しておきます. calc関数では, switch文を使って演算子の種別に応じた計算をしています. 対応していない演算子がある場合, コンパイラが怒ってきます.


Token型を導入したことにより, 演算子を追加した場合の対応漏れを防ぐことができます. このようにすれば, つまらない不具合をコンパイル時に潰せるようになります. ただし, 型システムの恩恵に与るには, 型システムにチェックしてもらえるようにデータ型を定義する必要があり, それなりに経験が必要になります.

5)まとめ

この記事では, コマンドラインから入力した式をチェックするためにデータ型を導入しました. データ型を導入することで, プログラミング時にありがちな誤りをコンパイラにチェックさせる方法を確認しました. 

最後に, この記事で使用したNode.jsとTypeScriptのバージョンは次の通りです.

PS C:\TSWork\RPN> node --version
v14.17.4
PS C:\TSWork\RPN> .\node_modules\.bin\tsc --version
Version 4.3.5
PS C:\TSWork\RPN>

2021/09/01

TypeScript入門 第4回 コマンドライン引数

ここまで作成したソースコードを使えば計算はできるのですが, 式を変更する毎にソースコードの編集, コンパイル, 実行と手順が多いので使い易くありません. そこで, コマンドライン引数から数式を与えられるようにします. 

1)コマンドライン引数

process.argvを使えば, コマンドラインから引数を取得できます. index.tsに次のようなコードを記述して試してみます.

for (let arg of process.argv) {
    console.log('コマンドライン引数:', arg)
}

実行してみます.

PS C:\TSWork\RPN> .\node_modules\.bin\tsc
PS C:\TSWork\RPN> node .\dist\index.js 1 2 + 4 *
コマンドライン引数: C:\Program Files\nodejs\node.exe
コマンドライン引数: C:\TSWork\RPN\dist\index.js
コマンドライン引数: 1
コマンドライン引数: 2
コマンドライン引数: +
コマンドライン引数: 4
コマンドライン引数: *
PS C:\TSWork\RPN>

2)コマンドライン引数から式を取り出す

実行結果から, process.argvの3つ目以降の要素を配列として取り出せば, コマンドラインから数式を与えられます. つまり, process.argvの0番目と1番目の要素がジャマ, ということです. このような場合, sliceを使って配列をコピーします. index.tsファイルを次のように変更します.

let input = process.argv.slice(2, process.argv.length)
for (let arg of input) {
    console.log('コマンドライン引数:', arg)
}

実行してみます.

PS C:\TSWork\RPN> node .\dist\index.js 1 2 + 4 *
コマンドライン引数: 1
コマンドライン引数: 2
コマンドライン引数: +
コマンドライン引数: 4
コマンドライン引数: *
PS C:\TSWork\RPN>

この結果から, inputをそのままcalc関数に渡せば計算してくれます. 最終的なindex.tsファイルの内容は次のようになります.

// 補助関数
function calcAux(stack: number[], op: (a: number, b: number) => number) {
    let b = stack.pop()
    let a = stack.pop()
    if ('number' === typeof a && 'number' === typeof b) {
        stack.push(op(a, b))
    } else {
        throw new Error('数字が入ってないよ')
    }
}

// 配列を逆ポーランド記法で記述した式として計算する.
function calc(input: string[]) {
    let stack: number[] = []
    for (let value of input) {
        if (value === '+') {
            calcAux(stack, (a, b) => a + b)
        } else if (value === '-') {
            calcAux(stack, (a, b) => a - b)
        } else if (value === '*') {
            calcAux(stack, (a, b) => a * b)
        } else if (value === '/') {
            calcAux(stack, (a, b) => a / b)
        } else {
            let a = parseInt(value)
            stack.push(a)
        }
    }

    if (1 === stack.length) {
        return stack.pop()
        
    } else {
        throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?')
    }
}

let input = process.argv.slice(2, process.argv.length)
// 計算してみる
console.log('計算結果', calc(input))

実行してみます.

PS C:\TSWork\RPN> .\node_modules\.bin\tsc
PS C:\TSWork\RPN> node .\dist\index.js 1 2 + 4 *
計算結果 12
PS C:\TSWork\RPN> node .\dist\index.js 1 2 + 4 * 5 -
計算結果 7
PS C:\TSWork\RPN>

コマンドライン引数の内容に応じて計算することを確認できました.

3)まとめ

この記事では, コマンドライン引数に記述した式を計算するようにプログラムを修正しました. プログラムに式を与える方法として, ファイルからの読み込みも考えられます. しかし, TypeScriptというよりもNode.jsでのファイルの読み込みは, 非同期プログラミングが前提となり, 挫折しそうなポイントですので, 記事を改めてじっくりと考えたいと思います. 

コマンドラインから式を与えられるようにしましたが, 誤った式を与えた場合, 例えば対応していない演算子や数値以外の文字列が与えられた時, エラーを返しません. 次回の記事では, 式として妥当かどうかをチェックする仕組みを入れます. 

最後に, この記事で使用したNode.jsとTypeScriptのバージョンは次の通りです.

PS C:\TSWork\RPN> node --version
v14.17.5
PS C:\TSWork\RPN> .\node_modules\.bin\tsc --version
Version 4.3.5
PS C:\TSWork\RPN>

2021/08/28

TypeScript入門 第3回 関数定義

1)はじめに

前回, 逆ポーランド記法で記述した数式を計算するプログラムを作成しました. しかし, 計算する数式をソースコードに埋め込んであって, 数式を変更するためにはソースコードを編集してコンパイルしなくてはなりません. 使い物にならないので, プログラムとしての体裁を整えていきます.

2)関数の定義

計算する部分を関数として定義し, 計算する数式を引数として与えられるようにします. 関数の戻り値は, 計算した結果とします. 計算の過程を記録するスタックは, 関数の中で定義するローカル変数とします. ソースコードは次のようになりました.

// 配列を逆ポーランド記法で記述した式として計算する.
function calc(input: string[]) {
    let stack: number[] = []
    for (let value of input) {
        if (value === '+') {
            let a = stack.pop()
            let b = stack.pop()
            if ('number' === typeof a && 'number' === typeof b) {
                stack.push(a + b)
            } else {
                throw new Error('数字が入ってないよ')
            }
        } else if (value === '*') {
            let a = stack.pop()
            let b = stack.pop()
            if ('number' === typeof a && 'number' === typeof b) {
                stack.push(a * b)
            } else {
                throw new Error('数字が入ってないよ')
            }
        } else {
            let a = parseInt(value)
            stack.push(a)
        }
    }

    if (1 === stack.length) {
        return stack.pop()
    } else {
        throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?')
    }
}

// 計算してみる
console.log(calc(['2', '3', '+', '4', '*']))

関数名は「calc」としました. 注意点は次の通りです.

  1. 引数名に続けて引数の型を指定する必要があります. 関数の戻り値の型も指定できますが, コンパイラに型推論させる方が良いようです.
  2. 計算結果をコンソールへ出力するコードは削除しました.
  3. 入力をすべて処理できれば, 計算結果を返します. この時, スタックの要素数が1であることを確認し, 1でない時はエラーを投げています.
  4. 関数定義に続いて, 動作確認用のコードを入れています.

このソースコードをコンパイルし実行すると, 計算結果として「20」を表示しました. 問題なく動作しています.

3)演算子の種類を増やす

今のソースコードでは, 対応している演算子は「+」と「×」の2種類だけです. 少なくとも「-」と「/」は対応しておきたいところです. 「+」や「×」を計算するコードをコピーして編集すれば簡単に対応できますが, 似たようなコードが繰り返し現れるのは気持ちの良いものではありません. そこで, 補助関数を導入して全体を見やすくします. 合わせて, 「-」と「/」にも対応します. ソースコードは次のようになりました.

// 補助関数
function calcAux(stack: number[], op: (a: number, b: number) => number) {
    let b = stack.pop()
    let a = stack.pop()
    if ('number' === typeof a && 'number' === typeof b) {
        stack.push(op(a, b))
    } else {
        throw new Error('数字が入ってないよ')
    }
}

// 配列を逆ポーランド記法で記述した式として計算する.
function calc(input: string[]) {
    let stack: number[] = []
    for (let value of input) {
        if (value === '+') {
            calcAux(stack, (a, b) => a + b)
        } else if (value === '-') {
            calcAux(stack, (a, b) => a - b)
        } else if (value === '*') {
            calcAux(stack, (a, b) => a * b)
        } else if (value === '/') {
            calcAux(stack, (a, b) => a / b)
        } else {
            let a = parseInt(value)
            stack.push(a)
        }
    }

    if (1 === stack.length) {
        return stack.pop()
        
    } else {
        throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?')
    }
}

// 計算してみる
console.log(calc(['2', '3', '+', '4', '*', '5', '/', '2', '-']))

補助関数の引数は, スタックと演算子に対応した関数を取れるようにしています. このようなプログラミング技法に慣れていない方は, 関数型プログラミングに関する書籍などで勉強することをお勧めします. 

補助関数の注意点として, スタックからポップした値を変数に割り当てる順序を「a, b」から「b, a」に変更しています. 引き算や割り算では, この順序を間違えると正しい答えは得られません. 補助関数を使うことで, calc関数の見通しが良くなりました. このソースコードをコンパイルし実行すると, 計算結果として「2」を表示しました. 問題なく動作しています.

3)まとめ

この記事では, 前回, 作成したプログラムの構成を変更し関数を定義して, プログラムらしく仕上げました. また, 計算のための補助関数は, 関数そのものを引数として与えられるように定義しました. TypeScriptのように型を厳密にチェックしてくれるプログラミング言語であれば, このようなテクニックを安心して使えます. 

しかし, 計算する式を変更するためにソースコードを編集するのは煩わしくなってきました. 次回の記事では, ソースコードを編集せずに計算する式を変更する方法を試します.

最後に, この記事で使用したNode.jsとTypeScriptのバージョンは次の通りです.

PS C:\TSWork\RPN> node --version
v14.17.5
PS C:\TSWork\RPN> .\node_modules\.bin\tsc --version
Version 4.3.5
PS C:\TSWork\RPN>

2021/08/25

TypeScript入門 第2回 逆ポーランド記法

新しいプログラミング言語の勉強を始める時は, 簡単なプログラムを作成するようにしており, 逆ポーランド記法(Reverse Polish Notation, RPN)で書かれた数式を計算するプログラムを作成することから始めています. 逆ポーランド記法に馴染みのない方もおられると思うので, 逆ポーランド記法の説明から始めます. なお, 記事が長くなるので分割します. 

1)逆ポーランド記法

多くのプログラミング言語では, 算数や数学の授業で習った数式と同じ形式で数式を書きます. 例えば, 2と3の足し算は次のように書きます.

2+3

この式に4を掛けてみます.

(2+3)×4

2+3×4

「2+3」全体に4を掛ける場合と3だけに4を掛ける場合とで計算結果は異なります. 一般に使う数式では, 次の事柄を把握しておかないと計算結果がとんでもないことになります.

  1. +や×などの演算子の優先順位
  2. カッコの有無や入れ子の具合

通常の数式では, +や×などの演算子は数字と数字の間に置きますが, 逆ポーランド記法では演算子を最後に置きます. 例えば, 「2+3」を次のように書きます.

2 3 +

「2に3を足す」と日本語の語順通りに読めます. 同じようにして, 「(2+3)×4」と「2+3×4」も逆ポーランド記法で書いてみます.

2 3 + 4 ×

2 3 4 × +

上の式は「2に3を足して, その結果に4を掛ける」, 下の式は「2に, 3と4を掛けた結果を足す」と読めます. 賢明なる読者の皆様はすでにお気づきの通り, 逆ポーランド記法で書いた式にはカッコが現れていません. また, 演算子の優先順位も考えなくて済みます. 

2)スタック

逆ポーランド記法で記述した式を計算する手順, 即ちアルゴリズムを考えていきたいのですが, その前にスタック(Stack)と呼ばれるデータ構造を確認する必要があります.

スタックは, 机の上に積んだ本をイメージすると分かりやすいと思います. 

スタックに対して行える操作は次の2つです.

  1. プッシュ(Push):積んである本(つまり, スタック)に新しい本を載せる.
  2. ポップ(Pop):スタックの1番上の本を取る.

スタックでは, 一番上に積んである本に対してのみ働きかけられ, 途中の本については何もできません. スタックの一番底にある本を取り出すためには, 1番上にある本から順に取り除く必要があります. 

3)アルゴリズム

スタックを用いれば, 逆ポーランド記法で記述した式を計算するアルゴリズムを簡潔に述べられます. 

  1. 式を構成する数値や演算子といった要素を順番に処理します.
  2. 数値の場合, スタックにプッシュします.
  3. 演算子の場合, スタックから数値を2つポップし, 演算子に応じた計算をした結果をスタックにプッシュします.
  4. 式の終端に到達した場合, スタックには1つだけ数値が残っており, その数値が求める答えです. 
  5. 計算の途中で空になったスタックからポップした場合や, 最後に2つ以上の数値がスタックに残っている場合, 不正な式と判断できます. 

4)試してみる

TypeScriptの配列をスタックとして使うためのpushメソッドとpopメソッドがあります. これらを使ってアルゴリズムを試してみます. 

まず, TypeScriptのプロジェクトを作成します. ディレクトリ名は逆ポーランド記法ということで「RPN」としています. プロジェクトの作成方法については, 前回の記事を参照してください. とりあえず, 関数の定義とかも考えず, 入力となる式もソースコードに埋め込んで, 計算できるものを作ります. でき上ったプログラムは次の通りです.

console.log('逆ポーランド記法')

// 入力となる式
let input = ['2', '3', '+', '4', '*']
console.log('入力', input)

// 計算のためのスタック
let stack: number[] = []
console.log('スタック', stack)

console.log('計算開始')
for (let value of input) {
    console.log('入力値', value)
    if (value === '+') {
        let a = stack.pop()
        let b = stack.pop()
        // let c = a + b
        if ('number' === typeof a && 'number' === typeof b) {
            stack.push(a + b)
        } else {
            throw new Error('数字が入ってないよ')
        }
    } else if (value === '*') {
        let a = stack.pop()
        let b = stack.pop()
        if ('number' === typeof a && 'number' === typeof b) {
            stack.push(a * b)
        } else {
            throw new Error('数字が入ってないよ')
        }
    } else {
        let a = parseInt(value)
        stack.push(a)
    }
    console.log('スタック', stack)
}

console.log('計算終了')
console.log('スタック', stack)

コンパイルして実行してみます.

PS C:\TSWork\RPN> .\node_modules\.bin\tsc
PS C:\TSWork\RPN> node .\dist\index.js
逆ポーランド記法
入力 [ '2', '3', '+', '4', '*' ]
スタック []
計算開始
入力値 2
スタック [ 2 ]
入力値 3
スタック [ 2, 3 ]
入力値 +
スタック [ 5 ]
入力値 4
スタック [ 5, 4 ]
入力値 *
スタック [ 20 ]
計算終了
スタック [ 20 ]
PS C:\TSWork\RPN>

期待通りの結果が得られました.

ソースコードでは, 演算子が入力された場合, スタックから数値をポップしています. この時, スタックが空の場合があるため, ポップした結果がundefinedである可能性があります. そのまま計算しようとすると, TypeScriptに怒られてしまいます.

TypeScriptのコンパイラ報告するエラーは事前に潰さなくてはコンパイルすらできません. TypeScriptでは, このようなコンパイル時のエラーに対応するためのソースコードを丁寧に書いて, コンパイラを納得させてからようやく実行できます. これが煩わしく感じられる人もいると思いますが, リリース後にこのような不具合が見つかることを考えると, コンパイラのご機嫌を取るぐらい何ともありません. 逆に, 慣れてくるとコンパイラのチェックの甘いプログラミング言語は怖くて使えなくなります.

ソースコードはコピペしてもらっても良いのですが, 自分で入力することをお勧めします. 自分で入力することでコンパイラの応答を観察できるので, 理解がより深まります. 

5)まとめ

今回の記事では, 逆ポーランド記法で記述した式を計算するためのアルゴリズムを確認しました. 次回の記事では, 作成したプログラムをそれらしく仕上げていきます.

最後に, この記事で使用したNode.jsとTypeScriptのバージョンは次の通りです.

PS C:\TSWork\RPN> node --version
v14.17.5
PS C:\TSWork\RPN> .\node_modules\.bin\tsc --version
Version 4.3.5
PS C:\TSWork\RPN>

2021/08/22

TypeScript入門 第1回 環境構築

1)はじめに

プログラムを実行する環境がデスクトップPCからスマートフォンに移っています. スマートフォンだけで動作するプログラムもありますが, スマートフォンはインターネットと常時接続できるため, サーバと協調して動作するプログラムも多くあります. スマートフォンが接続するサーバは, 自前で用意したオンプレミスのサーバよりもアマゾンのAWSやマイクロソフトのAzureといったクラウドサービスを利用したサーバが大半でしょう. 

このようにプログラムの開発・実行環境がデスクトップPCやオンプレミスのサーバからクラウド環境に移行しており, プログラマとして今後も仕事を続けていくためには, クラウド環境は避けて通れません. クラウド環境でプログラムを開発するのですから単純に仮想マシンを使うのではなく, AWS LambdaやAzure Functionsといったサービスを組み合わせて作りたいものです. そのために最適なプログラミング言語を選んでおきたいので, いろいろと検索して調べた結果, 型安全性を保証してくれるTypeScriptが良さそうな感じがします. 

15年ぐらい前にWebアプリケーションを開発するためJavaScriptを使って以来, JavaScriptから離れていましたので, TypeScriptはもちろんのこと最新のJavaScript事情やNode.jsについて何の知識もありません. 急ぎ書店で「プログラミングTypeScript」と「ハンズオンNode.js」を買い求め, この2冊を一通り読んでみましたが, 分かったような, 分からないようなモヤモヤした感じです. やはり, 簡単なもので良いのでTypeScriptを使ってプログラムを作らないとそのプログラミング言語を習得できません.

これに続く記事では, 私がTypeScriptに入門するにあたり, 試したこと, 考えたことを書きますので, 参考にしていただけると幸いです. なお, この記事では, TypeScriptでプログラムを開発するための環境を構築し, TypeScriptで書いたプログラムが動作するところまでを説明します.

2)環境構築

TypeScriptでプログラムを作成するために用意したものは次の通りです.

  • Node.js
  • Visual Studio Code

Node.jsはTypeScriptで書いたプログラムをJavaScriptにコンパイルし, 実行するために使います. ブラウザ上でTypeScriptを試せる「TypeScript Playground」もありますが, クラウドでサーバレスを目標にしていますので, Node.jsは必須でしょう. 

Visual Studio Code(以下, VSCodeと書きます)はソースコードを編集するためのテキストエディタです. テキストエディタは他にも色々とありますが, 情報が得られやすいVSCodeを選択しました. 

3)最初のプロジェクト

TypeScriptでプログラムを作成するための準備から始めす. まず, エクスプローラなどを用いて作業用のディレクトリを作成します. ディレクトリ名は「HelloTypeScript」としました. PowerShellを起動し, 作成したディレクトリに移動します. 続けて, 次の4つのコマンドを実行します. 

> npm init -y
> npm i -D typescript
> npm i -D @types/node
> npx tsc -init

1番目はプロジェクトの初期化, 2番目はTypeScriptコンパイラのインストール, 3番目はTypeScriptからNode.jsのAPIを使うための型情報のインストール, 4番目はTypeScriptのための初期化を実行しています. 4番目のコマンドはnpmでなくnpxであることに注意してください. lsコマンドを使ってディレクトリの中身を確認するといくつかのファイルができています. 


VSCodeを使って, tsconfig.jsonファイルを編集します. ここは, 「プログラミングTypeScript」の流儀に従って編集します. 編集結果は次の通りです.

{
  "compilerOptions": {
    "target": "es5",                                
    "module": "commonjs",                          
    "outDir": "./dist",     // コメントを外し, 出力ディレクトリにdistを指定             
    "strict": true,                             
    "esModuleInterop": true,                     
    "skipLibCheck": true,                          
    "forceConsistentCasingInFileNames": true      
  },
  "include": ["src"]        // 追加
}

tsconfig.jsonファイルの内容を見やすくするために, コメント行は削除しました. また, 編集した2行にコメントを付けています. 

tsconfig.jsonファイルの設定内容に合わせて, ディレクトリなどを作成します.

  1. distディレクトリを作成する.
  2. srcディレクトリを作成する.
  3. srcディレクトリ内にindex.tsファイルを作成する.
  4. index.tsファイルに「console.log('Hello TypeScript!')」と入力する.

これらのディレクトリやファイルは, VSCodeの画面左側, エクスプローラを使って作成できます. 作成した結果は次の通りです.


index.tsをコンパイルして実行します. PowerShellで次のコマンドを実行します.

> .\node_modules\.bin\tsc
> node .\dist\index.js

実行結果は次のようになります.


Windows10の設定によっては, tscを実行した時に次のようなエラーが表示されます.


このようなエラーが表示された場合, 

スタート→設定→更新とセキュリティ→開発者向け

の順に画面を開き, PowerShellスクリプトの実行を許可します.


ここまでできれば, TypeScriptで開発するための最低限の環境が整いました. 次回から簡単なプログラムを作成しながら, TypeScriptコンパイラの反応を観察し, 勘所を探っていきます.

2019/06/30

[読書] 「うつ」は炎症で起きる(エドワード・ブルモア 著)


著者は、ケンブリッジ大学精神医学科長であり、神経科学とメンタルヘルス分野のエキスパートです。著者の主張は、「うつ」は脳を含む体の炎症が引き起こす、というものです。

この30年、医療は劇的に進歩してきましたが、「うつ」などの心の病気に関しては停滞している状況です。この原因として、「うつ」に関する適切なバイオマーカーが無いことが挙げられます。

例えば、糖尿病であれば血糖値、高血圧であれば血圧、などを用いて病気の兆候などを判断できます。一方、「うつ」に関しては脳内のセロトニンが不足している、などと言われますが、脳内のセロトニンの濃度を正確に測る安価な方法はありません。つまり、薬が効いているかどうかを客観的に判定する基準がありませんでした。

また、体の病気や怪我と「うつ」などの心の病気は完全に分離して扱われてきました。これは、デカルトの二元論に始まり、体を血と肉でできた機械とみなすことで医療は発展してきました。更に、血液脳関門によって、白血球などの免疫細胞や免疫タンパク質が脳への侵入を防いでいると考えられていたため、体と心を分けて扱う根拠となっていました。

しかし、最近の研究により、血液脳関門は免疫細胞や免疫タンパク質を通過させており、また、体の炎症が「うつ」の原因となっていることを示す証拠が出てきています。これらについては、神経免疫学という分野で研究が進んでいます。つまり、体の炎症が脳に伝わり、脳が炎症を起こした結果が「うつ」であると考えられます。

なぜ、体が炎症を起こすと「うつ」になるのか、現代の生活では「うつ」になるメリットは何一つありませんが、かつてサバンナで暮らしていたときには「うつ」になるメリットがあったようです。

サバンナで部族社会の一員として暮らしていたとすると、狩猟や部族間の抗争により傷を負うことがあります。傷を負えば感染症にかかる可能性が高まるため、免疫系が活発になり体に炎症反応が生じます。この時、傷の回復に専念するため、体を休めなくてはなりません。つまり、「うつ」の症状を出すことで体を休めることができます。

このようなことから、体の炎症反応の度合いを測定することが「うつ」のバイオマーカーとなる可能性があります。これについては、神経免疫学の進展を待たねばなりません。

体の炎症を抑えれば心の健康にも良さそうです。研究の成果が出てくるまで、10年単位の時間が必要となりますので、それを待ちたくありません。体の炎症を抑える方法として、ヴィム・ホフ・メソッドがあります。これについては、改めて書きます。

ヒューマン・リソース・マシーン 入社41年目−並べ替えよ

目次 1)課題 0を終端とした文字列がいくつか流れてきます。各文字列に対してソート(並べ替え)を行い、小さい順(昇順)に右側へ運んでください。 2)状況の確認 この問題では, 予めコードが入っています. このコードを実行して, 何をするコードなのか確かめます.  左のコンベアから...