LoginSignup
0
0

More than 3 years have passed since last update.

ジェネリクス TypeScript & Angular 勉強会 #4

Last updated at Posted at 2020-08-23
1 / 11

前回のおさらい

Typescriptの関数定義は、以下のようなパターンがあります。

//一般的な関数定義
function greet(name: string): void {
  console.log(`hello, ${name}`)
}

// 関数式
let greet2 = function(name: string): void {
  console.log(`hello, ${name}`)
}

// アロー関数
let greet3 = (name: string) => {
  console.log(`hello, ${name}`)
}

// アロー関数の省略記法
let greet4 = (name: string) => console.log(`hello, ${name}`)

迷ったらアロー関数で定義しておくと一旦はよいです。 this をきっちり利用する関数を使いたい場合には function を使いましょう。

関数定義のおさらい

以下のような関数 filter を考えます。

  • 第一引数に、配列を取る
  • 第二引数に、その配列に対するフィルタ関数を取る

上記の条件で関数の型を考えてみましょう。

//アロー関数定義
let filter: (
    source: unknown[], //引数① もとの配列
    predicate: (input: unknown) => boolean //引数② フィルタリング用の関数
) => unknown[] //引数③ リターン

ただし、これだと型の束縛がないので、例えばこのような関数も代入できてしまいます。

filter = (
    src: unknown[],
    pred: (input: string) => boolean 
): number[] => { //return と pred の型が一致していない
    return []
}

もっと型を束縛すればいいのですが、そうすると汎用性がなくなります。

let filter2: (
    source: number[], //引数① もとの配列
    predicate: (input: number) => boolean //引数② フィルタリング用の関数
) => number[] //引数③ リターン

前回話した関数のオーバーロードにて、複数の型の定義はできますが冗長です。

// 関数のオーバーロード
type Filter = {
    (src: number[], pred: (input: number) => boolean): number[]
    (src: string[], pred: (input: string) => boolean): string[]
    (src: object[], pred: (input: object) => boolean): object[]
}

これらの冗長な定義を解決するのが、多くの言語で採用されているジェネリクスです。


ジェネリクス

ジェネリクスとは、一部の型を後から決められるような型のことです。
Filter関数は、ジェネリクスを用いて以下のように定義できます。

type filter3<T> = (
    source: T[], //引数① もとの配列
    predicate: (input: T) => boolean //引数② フィルタリング用の関数
) => T[] //引数③ リターン

上記における T を 「型パラメータ」と呼びます。


ジェネリクスの利用

ジェネリクスを利用するには、型を以下のように束縛する必要があります。

let filter4: filter3<number> = (src, pred) => {
    return src.filter(v => pred(v))
}

ただし、型が推論できる場合には自動で型束縛を行うこともできます。

function filter<T>(src: T[], func: (input: T) => boolean):T[] {
    return [] //仮実装
}

filter([1,2,3,4,5], (_) => true)

ジェネリクスの定義方法

以下のように、様々な形での定義が可能です。

type Filter1<T> = {
    (src: T[], pred: (input: T) => boolean) : T[]
}

type Filter2 = {
    <T>(src: T[], pred: (input: T) => boolean) : T[]
}

//多分これが一番一般的
type Filter3<T> = (src: T[], pred: (input: T) => boolean) => T[]

type Filter4 = <T>(src: T[], pred: (input: T) => boolean) => T[]

function filter5<T>(src: T[], pred: (input: T) => boolean): T[]{
    return []
}

デフォルト引数のように、型パラメータの初期値を設定することもできます。

type FilterWithDefault<T = number> = (src: T[], pred: (input: T) => boolean) => T[]

複数の型パラメータを持つジェネリクス

ジェネリクスには、複数の型パラメータをもたせることもできます。
例えば、以下のような関数 map を考えてみましょう。

  • ソースとなる配列を第一引数に、配列の要素を変換する関数を第二引数に取る
  • 変換された結果を戻り値とする

この場合、ソースの配列の型だけでなく、戻り値の型も指定をする必要があります。

type Map<T,U> = (src: T[], func:(input:T) => U) => U[]


ジェネリック型エイリアス

ここまで関数を中心に見てきましたが、関数以外の型に対してもジェネリック型の定義はできます。
たとえば、複数のFilter を管理する FilterType という型を考えてみましょう。

type Filter<T> = (src: T[], pred: (input: T) => boolean) => T[]
type FilterPipe<T> = {
    filters: Filter<T>[]
    apply: (src: T[]) => T
}

制限付きのジェネリクス型

ジェネリクスを利用したいときでも、あらゆる値が入るのではなく、特定の型に制限したい場合があります。
例えば、これまで扱ってきた Filter について、文字列と数値にしか適用したくない、という形になった場合、以下のような書き方で制限することができます。


type LimitedFilter<T extends string | number> = (src: T[], pred: (input: T) => boolean) => T[]

type stringFilter = LimitedFilter<string>
type objectFilter = LimitedFilter<Object> //Type 'Object' is not assignable to type 'number'.(2344)

練習問題

プログラミングTypescriptより

型安全なアサーション関数、isを実装してください。型で概略を記述することから始めます。
これは、完成したら、次のように使えるものです。


// stringとstringを比較します 
is('string', 'otherstring') // false

// booleanとbooleanを比較します
is(true, false) // false

// numberとnumberを比較します
is(42, 42) // true

// 異なる型同士を比較すると、コンパイル時エラーになります
is(10, 'foo') // エラー TS2345: 型 '"foo"' の引数を型 'number' の
              // パラメーターに割り当てることはできません。

回答例

function is<T>(left: T, right: T):boolean {
  return left === right
}

演習問題

練習問題のコードを改良して、任意の数の引数を渡せるようにしてください。

is(1, 2, 3) //false
is("type", "type", "type", "type") //true
is("type", 123, {}) //Error
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0