TypeScriptハンズオン 学習メモ 3.関数をマスターする

# 関数の基本

## 関数とは？

- 関数の基本形

```ts
function hello(name:string) {
    console.log("Hello, " + name + "!")
}

hello("Taro") // "Hello, Taro!" 
hello("Hanako") // "Hello, Hanako!" 
```

## 関数内の変数スコープ

- var
    - 関数の外で宣言した場合、そのソースコード全体で利用できる。関数内で宣言したものは、関数内のみ。
- let
   - その変数を宣言した構文内（`{}`）のみ。

## 戻り値について

- `function 関数名(引数):型 { 関数の処理 }`
- 戻り値がない場合は、`void`を指定する

```ts
function total(max:number):number {
    let num = 0
    for (let i = 0; i <= max; i++) {
        num += i
    }
    return num
}

function printTotal(n:number):void {
    console.log(n + "までの合計:" + total(n))
}
printTotal(10) // "10までの合計:55" 
printTotal(123) // "123までの合計:7626" 
```

## 複数の値を返す

- タプルで返す
- 定義
    - `function 関数名(引数):[ タプル ] { 関数の処理 }`
- 呼び出し
    - `let [ 変数1, 変数2, .. ] = function 関数名(引数)`


```ts
function calcTax(totalPrice:number):[price:number, tax:number] {
    const price = totalPrice / 1.1
    const tax = totalPrice - price
    return [price, tax]
}

function printCalcTax(totalPrice:number):void {
    const [price, tax] = calcTax(totalPrice)
    console.log(totalPrice + "の本体価格:" + price + ", 税額:" + tax)
}
printCalcTax(3980) // "3980の本体価格:3618.181818181818, 税額:361.818181818182" 
printCalcTax(9999) // "9999の本体価格:9090, 税額:909" 
```

## 引数に条件型を使う

- `function 関数名(引数1:型1 | 型2, 引数2:型3 | 型4)`

```ts
function menu(a: "yama" | "umi") {
    console.log(a)
}

menu("yama")
menu("kawa") // エラー
```

## オプション引数

- `function 関数名(引数1, 引数2?, 引数3?,   ..)`
- `function 関数名(引数1 = 初期値1, 引数2 = 初期値2  ..)`

```ts
function showWindow(width:number = 100, height:number = 50) {
    console.log(width, height)
}
showWindow(80) // 80, 50
showWindow() // 100, 50
```

## 可変長引数

- `function 関数名(...名前:型[])`

```ts
function printTotal(...n:number[]) {
    let total = 0
    for (let i = 0; i < n.length; i++) {
        total += n[i]
    }
    console.log(total)
}
printTotal(2,3,5,7) // 17
```

# 関数をさらに掘り下げる

## 無名関数について

- `const 定数 = function (引数):戻り値 {処理}`

## アロー関数

- `const 定数 = (引数):戻り値 => 処理`
- 関数はfunction型の値
- functionとアロー関数の違いは、アロー関数は変数の代入前（アロー関数定義の前）には使用できないこと。

```ts
const f = (name:string):void => {
    console.log("Hello, " + name + "!")
}

f("taro") // "Hello, taro!" 
f("ichiro") // "Hello, ichiro!" 

console.log(typeof(f)) // "function" 
```

## 内部関数について

- 関数内で関数を定義して使う

```ts
const f = (n1:number, n2:number):void => {
    const inF = (n:number):void => {
        console.log("answer:" + n)
    }
    const sum = n1 + n2
    inF(sum) // 内部関数を使用する
}

f(1 ,2) // "answer:3" 
```

## 引数に関数を使う

- 引数を指定しない
    - `const 定数 ＝ (引数:Function):戻り値の型 => 処理`
- 引数を指定する
    - `const 定数 ＝ (引数:(引数:型)=>戻り値の型):戻り値の型 => 処理`
- 下の例だと、`(...x:number[])=>number` が `f` の型

```ts
const calc = (f:(...x:number[])=>number, ...n:number[]):void => {
    const result = f(...n)
    console.log("answer:" + result)
}

const sum = (...s:number[]) => {
    let r = 0
    s.forEach(l => r += l)
    return r
}
const avg = (...s:number[]) => {
    let r = 0
    s.forEach(l => r += l)
    return r / s.length

}

calc(sum, 1, 2, 3) // "answer:6" 
calc(avg, 1, 2, 3) // "answer:2" 
```

## 戻り値に関数を使う

- f1、f2は関数内に0.1、0.08を保持している
- `(n:number)=>number` が戻り値の型

```ts
const f = (tax:number):(n:number)=>number => {
    return (n:number) => n * (1 + tax)
}
// 10%を課税する関数を取得
const f1 = f(0.1)
// 8%を課税する関数を取得
const f2 = f(0.08)

console.log(f1(12345)) // 13579.500000000002 
console.log(f2(12345)) // 13332.6 
```

## クロージャについて

- 現在値`count`と増分`n`を関数内に保持している
- 定義された環境を保ち、その中で動く関数のことを **クロージャ** という
- 日本語では **関数閉包** という

```ts
const f = (n:number):()=>number => {
    let count:number = 0
    return ():number=> {
        count += n
        return count

    }
}
// 現在値を保持して、1ずつ増える関数を取得
const f1 = f(1)
// 現在値を保持して、2ずつ増える関数を取得
const f2 = f(2)
// 現在値を保持して、3ずつ増える関数を取得
const f3 = f(3)

for (let i = 0; i < 10; i++) {
    console.log(f1(), f2(), f3())
}
```

# 関数の高度な機能

## エラーと例外

- try構文

```ts
try {
  // エラーが発生するかもしれない処理 
} catch(e) {
  // エラー発生後の処理 
} finally {
  // 常に最後に実行する処理 
}
```

- 例外を発生させる

```ts
throw Error("エラーです")
```

- try構文の例

```ts
const f = (arr?:any[]):void => {

    try {
        if (typeof(arr) === "undefined") {
            throw Error("arrは空です")
        }

        for(let i of arr) {
            console.log(i)
        }
    } catch(e) {
        console.log(e)
    } finally {
        console.log("finally!")
    }
}

f(["OK", "NG"]) //  "ok" "ng" "finally!" 
f() // "arrは空です" "finally!"
```

## 総称型（ジェネリクス）について

- `function 関数名<T>( 引数 ) : 戻り値`
- Tでなくてもいい
- 複数の総称型を使うには、`<T,U,V>`のように書く

```ts
function getRnd<T>(values: T[]): T {
    const r = Math.floor(Math.random() * values.length)
    return values[r]
}

const data1 = [0, 2, 4, 6, 8, 10]
const data2 = ["グー", "チョキ", "パー"]
const data3 = [true, false]

for(let i = 0; i< 10; i++) {
    const ret1 = getRnd(data1)
    const ret2 = getRnd(data2)
    const ret3 = getRnd(data3)
    console.log(`${ret1}(${typeof ret1})  ${ret2}(${typeof ret2})  ${ret3}(${typeof ret3})`)
}

// "6(number)  グー(string)  true(boolean)" 
// "8(number)  グー(string)  true(boolean)" 
// "0(number)  パー(string)  false(boolean)" 
// "4(number)  グー(string)  true(boolean)" 
// "8(number)  チョキ(string)  true(boolean)" 
// "0(number)  チョキ(string)  true(boolean)" 
// "8(number)  チョキ(string)  false(boolean)" 
// "6(number)  パー(string)  true(boolean)" 
// "2(number)  グー(string)  false(boolean)" 
// "0(number)  パー(string)  false(boolean)" 
```

## ジェネレーターと遅延評価

- ジェネレータの作り方

```ts
function* 名前( 引数 )
  // ...処理...
  yield 値
```

- ジェネレータは処理を実行し、`yield`があることろで待ち状態になる。`.next()`で取り出されると次の`yield`まで進んで待ち状態になる。
- 使い方
    - `ジェネレータ = 関数()` でジェネレータを取得する
    - `ジェネレータ.next()` で値を取り出す


```ts
function* gen(n: number) {
    for (let i = 0; i < n; i++) {
        yield i
    }
}

const g = gen(5)

console.log(g.next()) // 0
console.log(g.next()) // 1
console.log(g.next()) // 2
console.log(g.next()) // 3
console.log(g.next()) // 4
console.log(g.next()) // undefined
```

## 非同期処理とPromise

```ts
// dミリ秒待ってから、１～nの合計の数を求める
const promiseFunc = (n: number, d: number): Promise<number> => {
    console.log(`start: ${n}`)
    return new Promise((resultFunc) => {
        let total = 0
        for (let i = 1; i <= n; i++) {
            total += i
        }
        setTimeout(() => {
            // thenの中の関数の関数を呼び出す
            resultFunc(total)
        }, d)
    })
}

// 結果を受け取る関数 Promise<number>なのでnumberの引数を取る
const cb = (total: number) => {
    console.log(`result: ${total}`)
}

// 300ミリ秒待って、1～10の合計を表示する（最後に表示）
promiseFunc(10, 300).then(cb)
promiseFunc(100, 200).then(cb)
promiseFunc(1000, 100).then(cb)
// 最初に表示される（最初に表示）
console.log("do something...")
```

# コンソールプログラムを作ろう

## コンソールプログラムの仕組み

- ターミナルのコマンドから渡される引数の情報は、`process.argv`から取得でき、テキストの配列になっている

```
process.argv[0] = nodeコマンドのパス
process.argv[1] = スクリプトのファイルのパス
process.argv[2]以降 = コマンド実行時に渡される引数
```

### 数字を素因数分解する

```ts
console.log("Node path = " + process.argv[0]);
console.log("script file path = " + process.argv[1]);

// 引数で渡された文字列を、数値の配列にする
const data: number[] = [];
for (let i = 2; i < process.argv.length; i++) {
  data.push(Number(process.argv[i]));
}
console.log(data);

// 画面から入力された数値を順に素因数分解する
for (let item of data) {
  // 素因数分解する
  const res = primeFactor(item);
  // 結果を画面表示する
  console.log(`${item} = ${res}`);
}

// 素因数分解する関数
function primeFactor(a: number): number[] {
  const v: number[] = [];
  let x = a;
  let n = 2;

  while (x > n) {
    // 2, 3, 5, と順に割れるか判定して割れたら、v[]に入れ、新たなxを代入する
    if (x % n == 0) {
      x = x / n;
      v.push(n);
    } else {
      // 割れない場合、2のとき1、3以上の時2を加算する
      n += n == 2 ? 1 : 2;
    }
  }
  // 現在の割る数(n)<=割られた数(x)になった場合、もう割れないので割られた数(x)をv[]に入れる
  v.push(x);
  return v;
}
// [ 10, 100 ]
// 10 = 2,5
// 100 = 2,2,5,5

```