第2回 TypeScript という言語
TypeScript & 関数型講座
今講座のコンセプト
これから TypeScript を使う人には
実践を想定した TypeScript の重要な機能を学習できるように
今 JavaScript, Java, Scala を使っている人には
それらと比較して TypeScript がどのような言語なのかを知ってもらえるように
TypeScript の概要
● TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーで
オープンソースのプログラミング言語
● TypeScript は JavaScript に対して、省略も可能な静的型付けとクラスベースオ
ブジェクト指向を加えた厳密なスーパーセットとなっている(いわゆる AltJS)
● つまり、型定義できる JavaScript
TypeScript の言語仕様
ただ一つの値のみとる型
他の言語ではあまり見られない(と思ったけど、Scala3 に入る予定)
リテラル型
const a = 100 // 100
const b = 'a' // a
const c = true // true
const d: 101 = 100 // 型 '100' を型 '101' に割り当てることはできません。
関数の型
// TypeScript
const test:(a: number, b: number) => number = (a, b) => a + b
// Scala
val test: (Int, Int) => Int = (a, b) => a + b
// Java
BiFunction<Integer, Integer, Integer> test = (a, b) -> a + b;
Java の関数は第一級オブジェクトではないが、関数型インターフェースというものを利
用して第一級オブジェクトっぽく見せている。
void 型
関数の返り値の型として使われ、「何も返さない」ことを表す
JavaScript では何も返さない関数は undefined となるため、void 型の値は
undefinde のみ。
function hello(): void {
console.log('hello')
}
Java でも同様に void が存在する。
Scala では Unit 型が大体同じことをしている。(厳密に void と Unit が同じなのかまで
は調べていない)
const a: any = 666 // any
const b: any = 'danger' // any
const c = a + b // any
any 型
プログラマの敗北
any を使うと TypeScript が持つ型の優位性が無くなり、ただの JavaScript と同じ振る
舞いになってしまう。そのため、やむをえない場合以外使うべきではない
型を無視するためのものなので、Java、Scalaにはない
プログラミングのスタイルの一種で、あるオブジェクトが特定のプロパティを持つことだ
けを重視し、その名前が何であるか(名前的型付け)は気にしない。一部の言語では
ダックタイピングと呼ばれている。
構造的型付け
class User {
constructor(public readonly name: string) {}
}
class Cat {
constructor(public readonly name: string) {}
}
const cat: Cat = new User('bob')
TypeScript で名前的型付け的なことをするには、ちょっとした工夫が必要。
構造的型付け
class User {
private readonly __nominal: void // private な要素を持たせることにより、他の構造と区別できる
constructor(public readonly name: string) {}
}
class Cat {
private readonly __nominal: void
constructor(public readonly name: string) {}
}
const cat: Cat = new User('bob')
// 型 'User' を型 'Cat' に割り当てることはできません。
// 複数の型に、プライベート プロパティ '__nominal' の異なる宣言が含まれています
型エイリアス
ある型を指し示す別名を宣言することができる
type User = {
name: string
}
class User2 {
constructor(public readonly name: string) {}
}
const a: User = new User2('bob') // 構造的型付けなので代入可能
型エイリアス
一応 Scala でも可能
type User = { def name: String }
case class User2(name: String) {}
val user: User = User2("bob")
Java には存在しない
interface
型エイリアスと同様に、構造に名前をつけるための方法。小さな差異がいくつかある
が、ほとんど型エイリアスと同じ。
interface User {
name: string
}
class User2 {
constructor(public readonly name: string) {}
}
const a: User = new User2('bob') // 構造的型付けなので代入可能
interface のメリット
定義と実装を分けることができ、プログラムを柔軟に保てる。
interface JournalRepository { getById: (id: number) => Journal }
class HttpJournalRepository implements JournalRepository {
getById(id: number): Journal { // サーバーからデータ取得 }
}
class InmemoryJournalRepository implements JournalRepository {
getById(id: number): Journal { // インメモリーでデータ取得 }
}
// 実装を切り替えても、プログラムの変更は必要ない
function getJournal(id: number, repository: JournalRepository): Journal {
return repository.getById(id)
}
interface VS class
interface は複数継承することが可能。そのため、一つのオブジェクトに複数の振る舞
いを持たせることができる。
複数の場所で型レベルの制約を強制するために使われるプレースホルダーの型。
ジェネリック型
class Some<T> {
constructor(public readonly value: T) {}
map<U>(f: (v: T) => U): Some<U> {
return new Some(f(this.value))
}
}
ジェネリック型パラメータに制限を加えられる
型境界
class A { private type = 'A' }
class B {}
class C extends A {}
class Some<T extends A> {
constructor(public readonly value: T) {}
}
const someB = new Some(new B())
// 型 'B' の引数を型 'A' のパラメーターに割り当てることはできません。
// プロパティ 'type' は型 'B' にありませんが、型 'A' では必須です。
const someC = new Some(new C())
配列のサブタイプで、固定長の配列を型付けするための型。
タプル型
const a: [string, boolean] = ['test', true]
const b: [string, boolean] = ['test', true, 1]
// 型 '[string, true, number]' を型 '[string, boolean]' に割り当てることはできません。
// ソースには 3 個の要素がありますが、ターゲットで使用できるのは 2 個のみです。
const c: [string, boolean] = ['test', 1]
// 型 'number' を型 'boolean' に割り当てることはできません。
Java にはない。
あくまで型を定義するのが面倒な場合の代替手段。多用は厳禁(Elm のようにタプル
の要素は 3 つまで、と要素数の制限を入れている言語もある)
合併型
A と B という 2 つのものがある場合、それらの合併(union)とは、それらの和(A, B, ま
たはその両方に含まれる全てのもの )を指す。
TypeScript では、型の合併を表現する特別な型演算子が用意されている。
type Cat = { name: string, purrs: boolean }
type Dog = { name: string, barks: boolean }
type CatOrDog = Cat | Dog
const test1: CatOrDog = { name: 'coco', purrs: true } // OK
const test2: CatOrDog = { name: 'boss', barks: true } // OK
const test3: CatOrDog = { name: 'boss', purrs: false, barks: true } // OK
const test4: CatOrDog = { name: 'boss' }
// 型 '{ name: string; }' を型 'CatOrDog' に割り当てることはできません。
// プロパティ 'barks' は型 '{ name: string; }' にありませんが、型 'Dog' では必須です。
合併型
こんなことも。
function test(value: string | number): null | number {
if (typeof value === 'number') {
return 10
} else {
return null
}
}
function test2(): void {
const result = test(1) // const result: number | null
result.toString() // NG Object is possibly 'null'.
}
合併型
Scala3 で取り入れられる予定。
Java には存在しない。
function test(a: number | string | boolean | null): void {
if (a == null) return console.log('null')
const b = a // const b: string | number | boolean
if (typeof a === 'number') return console.log('number')
const c = a // const c: string | boolean
if (typeof a === 'string') return console.log('string')
const d = a // const d: boolean
}
型の絞り込み
TypeScript はフローベースの型推論を行う。これにより、型の絞り込みを提供する。
trait 積木
case object 三角の積み木 extends 積木
case object 四角の積み木 extends 積木
case object 丸の積み木 extends 積木
trait Tree[T]
case class Leaf[T](value: T) extends Tree[T]
case class Branch[T](value: Seq[Tree[T]]) extends Tree[T]
タグ付き合併型
TypeScript で代数的データ型を表現する方法
代数的データ型とは、総体と、それを構成する構成子からなるデータ構造である
interface 三角の積み木 {
readonly type: '三角の積み木'
readonly a: string
}
interface 四角の積み木 {
readonly type: '四角の積み木'
readonly b: number
}
interface 丸の積み木 {
readonly type: '丸の積み木'
readonly c: boolean
}
type 積木 = 三角の積み木 | 四角の積み木 | 丸の積み木
タグ付き合併型
TypeScript では、文字列リテラルを用いた「タグ」というものを利用して表現する
// Not all code paths return a value.
function test(value: 積木) {
switch (value.type) {
case '三角の積み木':
return value.a // 型の絞り込みにより、三角のプロパティが使用できる
case '四角の積み木':
return value.b
}
}
タグ付き合併型
代数的データ型のメリットは、パターンマッチでの網羅チェックを行えることである。(要
出典)あと、再帰的データ構造を簡単に定義できる。
※ tsconfig.json で noImplicitReturns を true にする必要あり
交差型
A と B という 2 つのものがある場合、それらの交差(intersection)とは、それらの積
(A と B両方に含まれる全てのもの )を指す。
TypeScript では、型の交差を表現する特別な型演算子が用意されている。
type Cat = { name: string, purrs: boolean }
type Dog = { name: string, barks: boolean }
type CatAndDog = Cat & Dog
const test1: CatAndDog = { name: 'coco', purrs: true } // NG
const test2: CatAndDog = { name: 'boss', barks: true } // NG
const test3: CatAndDog = { name: 'boss', purrs: false, barks: true } // OK
const test4: CatAndDog = { name: 'boss' } // NG
function isNonEmpty<A>(
fa: ReadonlyArray<A>
): fa is ReadonlyNonEmptyArray<A> {
return fa.length > 0
}
function test(value: ReadonlyArray<string>) {
if (isNonEmpty(value)) {
const a = value // const a: ReadonlyNonEmptyArray<string>
}
}
ユーザー定義型ガード
引数を 1 つ取り、boolean を返す関数は、その引数の型を絞り込み可能にできる
readonly 修飾子
フィールドに初期値を割り当てた後でそのフィールドを変更できないことを宣言できる。
オブジェクトを不変にするために非常に重要。
const user: {
readonly name: string
} = {
name: 'bob'
}
user.name = 'alice' // 読み取り専用プロパティであるため、 'name' に代入することはできません。
オプション (?) 修飾子
TypeScript に、あるものが省略可能であることを伝える修飾子
function test(a?: number): number {
return a
}
// Type 'number | undefined' is not assignable to type 'number'.
// Type 'undefined' is not assignable to type 'number'
function test2(a?: number): number {
if (a === undefined) {
return 0
} else {
return a
}
}

TypeScript & 関数型講座 第2回 TypeScript という言語