TypeScript Handbook を読む (5. Functions)

[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html) を読み進めていく第五回目。

1. [Basic Types](http://qiita.com/murank/items/9c1f633545842c953947)
1. [Variable Declarations](http://qiita.com/murank/items/913f1c1e4e725047436c)
1. [Interfaces](http://qiita.com/murank/items/553276c3dc2eec9bfe47)
1. [Classes](http://qiita.com/murank/items/7e8f15225bc8259e9bf3)
1. Functions (今ココ)
1. [Generics](http://qiita.com/murank/items/144b686d34fd433a97a3)
1. [Enums](http://qiita.com/murank/items/cc487562b21bae3728ba)
1. [Type Inference](http://qiita.com/murank/items/e67ad7426020321228e9)
1. [Type Compatibility](http://qiita.com/murank/items/606efb0615294a6826e3)
1. [Advanced Types](http://qiita.com/murank/items/bc1776efbecbd2120d94)
1. [Symbols](http://qiita.com/murank/items/04b3b31caa677750f240)
1. [Iterators and Generators](http://qiita.com/murank/items/7ceec53dde886f31a682)
1. [Modules](http://qiita.com/murank/items/0fc4e059be8ea5e1c274)
1. [Namespaces](http://qiita.com/murank/items/0dfad4a0bb91aaa6c97b)
1. [Namespaces and Modules](http://qiita.com/murank/items/0be1978a3515fc76e7ba)
1. [Module Resolution](http://qiita.com/murank/items/5f11466474d49498bbd5)
1. [Declaration Merging](http://qiita.com/murank/items/ec90ba04edab1ea3718e)
1. [JSX](http://qiita.com/murank/items/90164aa9a3bed3c49a80)
1. [Decorators](https://qiita.com/murank/items/580e34883e0832ca7aed)
1. [Mixins](https://qiita.com/murank/items/e77ad8359ed1fd82822d)
1. [Triple-Slash Directives](https://qiita.com/murank/items/faff57883338ebdeda12)
1. [Type Checking JavaScript Files](https://qiita.com/murank/items/588eaddfe8af59914658)


# Functions
[原文](https://www.typescriptlang.org/docs/handbook/functions.html)

## Functions
TypeScript では JavaScript と同じように名前付き関数と無名関数の両方を使用することができます。

```ts:TypeScript
// 名前付き関数
function add(x, y) {
    return x + y;
}

// 無名関数
let myAdd = function(x, y) { return x+y; };
```

また、JavaScript と同様に関数外の変数を `キャプチャ` して関数の中から参照することができます。

```ts:TypeScript
let z = 100;

function addToZ(x, y) {
    return x + y + z;
}
```

## Function Types
### Typing the function
関数の引数や戻り値の型を指定することができます。
この時、TypeScript は return 文から戻り値の型を推測するため、大抵の場合で戻り値の型を省略することができます。

```ts:TypeScript
function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number { return x+y; };
```

### Writing the function type
関数の型は引数の型と戻り値の型という 2 つのパートを持っています。
もし関数の完全な型を表現する場合、その両方が必須となります。

```ts:TypeScript
let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x+y; };
```
>`(x: number, y: number) => number` の部分が関数の型

引数の型を表現する時には、引数リストと同じように引数名と型を指定しますが、ここでの引数名は単に可読性を高めるためだけのものです。
そのため、先ほどの例は以下のようにも記述できます。

```ts:TypeScript
let myAdd: (baseValue:number, increment:number) => number =
    function(x: number, y: number): number { return x + y; };
```

戻り値の型は、引数の型と戻り値の型の間に二重矢印 (`=>`) を記述することで表現します。
関数の型では戻り値の型も必須なため、戻り値を持たない関数の場合には戻り値の型として `void` を使用します。

### Inferring the types
代入元/先のどちらからで型が省略された場合でも、TypeScript は型推論を行ってくれます。
これを "文脈に基づく型付け" と呼びます。

```ts:TypeScript
// myAdd は関数の型として推論される
let myAdd = function(x: number, y: number): number { return  x + y; };

// 引数 'x'、'y' は数値型として推論される
let myAdd: (baseValue:number, increment:number) => number =
    function(x, y) { return x + y; };
```


### Optional and Default Parameters
TypeScript では、デフォルトですべての引数が必須として扱われるため、関数を呼び出す際はすべての引数を指定する必要があります。

```ts:TypeScript
function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // エラー。引数が少ない
let result2 = buildName("Bob", "Adams", "Sr.");  // エラー。引数が多い
let result3 = buildName("Bob", "Adams");         // OK
```

JavaScript ではすべての引数が任意であり、指定しなかった引数は `undefined` になっていましたが、これを TypeScript で実現するには引数名の後ろに `?` を付与します。
ただし、任意の引数は必須の引数よりも **後ろ** に配置する必要がある点に注意してください。

```ts:TypeScript
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob");                  // 今回は OK
let result2 = buildName("Bob", "Adams", "Sr.");  // エラー。引数が多い
let result3 = buildName("Bob", "Adams");         // OK
```

引数が指定されなかった、または `undefined` が渡された場合のデフォルト値を指定することもできます。

```ts:TypeScript
function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // 今はOK。戻り値は "Bob Smith"
let result2 = buildName("Bob", undefined);       // これも動作する。戻り値は "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // エラー。引数が多い
let result4 = buildName("Bob", "Adams");         // OK。戻り値は "Bob Adams"
```
```js:JavaScript
function buildName(firstName, lastName) {
    if (lastName === void 0) { lastName = "Smith"; }
    return firstName + " " + lastName;
}
var result1 = buildName("Bob"); // 今はOK。戻り値は "Bob Smith"
var result2 = buildName("Bob", undefined); // これも動作する。戻り値は "Bob Smith"
var result3 = buildName("Bob", "Adams", "Sr."); // エラー。引数が多い
var result4 = buildName("Bob", "Adams"); // OK。戻り値は "Bob Adams"
```

必須の引数よりも後ろに配置されたデフォルト引数は、任意の引数として扱われるため、以下の 2 つの関数の型はともに `(firstName: string, lastName?: string) => string` となります。

```ts:TypeScript
function buildName(firstName: string, lastName?: string) {
    // ...
}
```
```ts:TypeScript
function buildName(firstName: string, lastName = "Smith") {
    // ...
}
```

任意の引数と異なり、デフォルト引数は必ずしも必須の引数の後ろに配置する **必要はありません**。
必須の引数の前の引数を省略する場合、明示的に `undefined` を指定します。

```ts:TypeScript
function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // エラー。引数が少ない
let result2 = buildName("Bob", "Adams", "Sr.");  // エラー。引数が多い
let result3 = buildName("Bob", "Adams");         // OK。戻り値は "Bob Adams"
let result4 = buildName(undefined, "Adams");     // OK。戻り値は "Will Adams"
```

### Rest Parameters
複数の引数をまとめて受け取りたい場合や引数の数が不定の場合には、JavaScript では `arguments` 変数を直接使用していましたが、TypeScript では引数名の前に省略記号 (`...`) を付与することで、配列としてまとめて引数を受け取ることができます。

```ts:TypeScript
function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
```
```js:JavaScript
function buildName(firstName) {
    var restOfName = [];
    for (var _i = 1; _i < arguments.length; _i++) {
        restOfName[_i - 1] = arguments[_i];
    }
    return firstName + " " + restOfName.join(" ");
}
var employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
```

省略記号を関数の型で使用することも可能です。

```ts:TypeScript
function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
```

## `this`
### `this` and arrow functions
JavaScript における `this` は非常に便利で柔軟な一方で、正しく使うには常に関数の実行コンテキストを意識しておく必要があります。

例えば、以下の例を実行するとエラーになります。
なぜなら、`createCardPicker` メソッドが返却している関数で使用されている `this` は `deck` ではなく、`window` を指しているためです。
これは `cardPicker` を直接呼び出したために起きたことで、このようなトップレベルの非メソッド構文呼び出しでは `this` に `window` が設定されます。(ただし、strict モードでは `window` ではなく `undefined` になります)
>*トップレベルの非メソッド構文呼び出し* とは `オブジェクト.メソッド()` や `関数.call/apply()` ではない、普通の関数呼び出しのことね

```ts:TypeScript
let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);
```

ECMAScript 6 のアロー構文を使用することでこの問題を回避できます。

```ts:TypeScript
let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // アロー関数を用いることで 'this' をキャプチャする
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);
```
```js:JavaScript
var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function () {
        var _this = this;
        // アロー関数を用いることで 'this' をキャプチャする
        return function () {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
            return { suit: _this.suits[pickedSuit], card: pickedCard % 13 };
        };
    }
};

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);
```

さらに、コンパイラへ `--noImplicitThis` フラグを設定することで、このような誤りを警告させることができます。
この例では、`this.suits[pickedSuit]` の `this` が `any` 型であると指摘されるでしょう。
>ここで言う「誤り」とは、オブジェクトリテラル内で `this` をキャプチャしたことで、`this` の型が `any` になってしまったことを指してるっぽい?
>`--noImplicitThis` フラグは `this` の型が `any` の時に警告するものだし。


### `this` parameters
前述の例ではオブジェクトリテラル内で関数式を使用したため、`this.suits[pickedSuit]` の型が `any` になってしまっていました。
これを修正するには明示的に `this` 引数を渡す必要があります。
`this` 引数はダミーの引数であり、引数リストの最初に指定する必要があります。

```ts:TypeScript
interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // この関数の呼び出し先が Deck であることを明示的に指定する
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);
```
```js:JavaScript
var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // この関数の呼び出し先が Deck であることを明示的に指定する
    createCardPicker: function () {
        var _this = this;
        return function () {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
            return { suit: _this.suits[pickedSuit], card: pickedCard % 13 };
        };
    }
};

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);
```
>`this` 引数は JavaScript コードからは削除されるのね。
>とは言え、毎回 `this` 引数を指定するのも大変なので、`--noImplicitThis` フラグを指定しつつ、警告が出たら `this` 引数を指定するくらいが落とし所かな。

### `this` parameters in callbacks
コールバック関数を指定するようなライブラリにおいても、`this` に関するエラーに巻き込まれるでしょう。
これを回避するためには、まず以下のように `this: void` と宣言することで、コールバック関数で `this` を使用しないことを明示します。

```ts:TypeScript
interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}
```

こうすると、以下のように誤って `this` を使用しようとしてもコンパイルエラーになります。

```ts:TypeScript
class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // おっと、ここで this を使おうとしている
        this.info = e.message;
    };
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // エラー!
```

このエラーを解消するには、以下のように `this` の型を変更します。

```ts:TypeScript
class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // this の型が void なので、this を使用することはできない!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
```

もしもコールバック関数の中で `this` を使用したい場合、以下のようにアロー関数を使用する必要があります。

```ts:TypeScript
class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}
```
```js:JavaScript
var Handler = (function () {
    function Handler() {
        var _this = this;
        this.onClickGood = function (e) { _this.info = e.message; };
    }
    return Handler;
}());
```

この方法のデメリットは、通常のメソッドは prototype に追加されることでインスタンス間で共有されるのに対し、各インスタンスごとに関数が作成されることです。
>つまりメモリが無駄に消費されるというわけ。
>まあインスタンスごとにキャプチャしている `this` が違うわけだから共有は無理よね。


## Overloads
JavaScript は非常に動的な言語であり、渡された引数の型に応じて同じ関数から異なる型を返却することは珍しくありません。

```ts:TypeScript
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): any {
    // 引数が object または array の場合、
    // デッキが渡されたとみなしてその中からカードを引く
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard; // 戻り値はデッキ内のインデックス (number)
    }
    // それ以外の場合、指定されたカードを引く
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 }; // 戻り値は引いたカード (object)
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
```

この関数で扱っている型を正確に表現するにはどうすれば良いでしょう?
その答えは関数のオーバーロードを使用することです。

```ts:TypeScript
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // 引数が object または array の場合、
    // デッキが渡されたとみなしてその中からカードを引く
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard; // 戻り値はデッキ内のインデックス
    }
    // それ以外の場合、指定されたカードを引く
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 }; // 戻り値は引いたカード (object)
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
```

オーバーロードは宣言された順番に、指定された引数にマッチするものがないか検索されるため、より限定的なものから順に宣言することが一般的です。

`function pickCard(x): any` はオーバーロードの中に含まれないため、オブジェクトと数値以外の引数で `pickCard` 関数を呼びだそうとするとエラーになります。

>いまいち明確にオーバーロードの記法が説明されていないけど、処理を含まない関数宣言だけを並べて、その後に処理を含む同名の関数を定義したら OK かな?
>また、実際に処理を行う関数については型を `any` にしなくても、オーバーロード宣言と互換性のある型なら良いようだ。
>となると、IDE の補完のことを考えて共用型とかにした方が良い気がする。

>オーバーロードがある他の言語に慣れていると引数が `any` のメソッドの中で型を基に分岐させなくても、それぞれの引数に応じた関数として実装すれば良いんでは? と思うかもしれないけど、元々 JavaScript にオーバーロードの考え方がないため、こうせざるを得ないみたい。