湯婆婆.js 湯婆婆.ts

## 前提条件

[偉大なる元ネタ](https://qiita.com/Nemesis/items/c7192a7c510788d2cba2)をはじめ、主な処理の流れはみなさん散々書いていらっしゃいますが、だいたいこんな感じ。

1. 出力「契約書だよ。そこに名前を書きな。」
2. ユーザからの入力を待つ
3. 受け取った文字列からランダムに1文字抽出
4. 出力「フン。`入力した文字列`というのかい。贅沢な名だねぇ。」
5. 出力「今からお前の名前は`抽出した1文字`だ。いいかい、`抽出した1文字`だよ。分かったら返事をするんだ、`抽出した1文字`!!」

動作環境：
[Google Chrome@最新版](https://www.google.co.jp/chrome/)
[node@v14.15.1](https://nodejs.org/ja/)
[npm@6.14.8](https://www.npmjs.com/package/npm)
[typescript@4.1.2](https://www.npmjs.com/package/typescript)

※ 便宜上、Node.jsもECMAもひっくるめてJavaScriptと呼称します。

早速実装していこうと思います。


## JavaScriptで湯婆婆を実装してみる

何はともあれ、超簡素なものを書いてみます。

```js
const input = window.prompt('契約書だよ。そこに名前を書きな。');
const index = Math.floor(Math.random() * input.length);
const moji = input[index];

window.alert(`フン。${input}というのかい。贅沢な名だねぇ。`);
window.alert(`今からお前の名前は${moji}だ。いいかい、${moji}だよ。分かったら返事をするんだ、${moji}!!`);
```

このコードで登場する関数はすべてJavaScriptにあらかじめ定義されているものです。
うーん。いかにも、JavaScriptらしいコード<ruby>面<rp>(</rp><rt>づら</rt><rp>)</rp></ruby>ですね。

なお、JavaScriptくんは良くも（？）悪くも開発者に寄り添いたい気持ちが強すぎて、よしなに解釈してくれてしまうので湯婆婆がクラッシュするなんてことが起こりません[^1]。

[^1]: JavaScriptにはJavaScript特有の`undefined`という、`null`ではないが`null`みたいな型が存在しますが、これを文字列と結合させようとすると暗黙の型変換が起こり、`undefined`型の値は`undefined`という`string`型になってしまいます。

```
フン。というのかい。贅沢な名だねぇ。
今からお前の名前はundefinedだ。いいかい、undefinedだよ。分かったら返事をするんだ、undefined!!
```

ただし、入力をせずにキャンセルボタンを押すかESCキーを押すと、`window.prompt()`の返り値は`null`になって無事に湯婆婆はクラッシュします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/7bacebb4-2538-b656-c6bb-77eaf267c69d.png)

**実にJavaScriptらしくて愛らしい**。

### 細かい話

JavaScriptでは`window.prompt()`でユーザの入力を`string`型で同期的に受け取ることができます。

次に、受け取った値から無作為に1文字を抽出します。
`string`型は配列に似ていて、`input[0]`のように0番始まりで1文字ずつ参照できるので、入力された文字数未満の数をランダムに作ります。

`Math.random()`は0〜1未満のランダムな数値を返します。
返り値は浮動小数点ですが、型は整数も小数点関係なく`number`型です。

文字数は、`string`型のインスタンスに生えているプロパティ`length`に入っているので、`Math.random() * 文字列.length`で、0〜文字数の範囲でランダムな数値を得ることができます。

最後に`Math.floor`で小数点の切り捨てを行い、文字列のインデックス番号を用意します。
そこまでできたら、あとは`window.alert()`で出力してフィニッシュです。

対戦ありがとうございました。

## 昨今のJavaScriptはブラウザの世界だけにとどまらない

みなさんご存知かと思われますが、JavaScriptには[Node.js](https://nodejs.org/ja/)というつよつよな環境があります。
その環境ではブラウザで動かすようなJavaScriptのコードとほとんど同じものを動かせるのですが、`window.prompt()`や`window.alert()`などは…というより`window`オブジェクトがそもそもないので動作させることができません[^2]。

[^2]: Node.jsでは`global`という名前のグローバル変数があり、JavaScriptで`window`に生えているメソッドやプロパティの多くは、`global`にも生えています

そこで続いては、CLIで呼び出しても動くようにしてみたいと思います。
まずはNode.jsをインストールして、`npm`コマンドが動くようにおきましょう（[Nodist](https://qiita.com/satoyan419/items/56e0b5f35912b9374305)などでNode.jsをインストールしておいた方が便利かも）。

こんな感じのコードを書いて、**「湯婆婆.js」という名前で保存**します。

```js:湯婆婆.js
import prompts from 'prompts';

const {input} = await prompts({
  type: 'text',
  name: 'input',
  message: '契約書だよ。そこに名前を書きな。',
});
const index = Math.floor(Math.random() * input.length);
const moji = input[index];

console.log(`フン。${input}というのかい。贅沢な名だねぇ。`);
console.log(`今からお前の名前は${moji}だ。いいかい、${moji}だよ。分かったら返事をするんだ、${moji}!!`);
```

次に、**そのjsファイルと同じ場所のpackage.jsonに**次の内容を追記します（なければ作成します[^3]）。

[^3]: 通常、一番最初は`npm init`で`package.json`を作成してから開発を進めます。`npm init -y`で諸々設定するのを省略することともできます。

```json:package.json
{
  "type": "module",
  "scripts": {
    "start": "node 湯婆婆"
  }
}
```

そして最後に、そのディレクトリで次のコマンドを打ちます。

```
npm i -save prompts 
```

ここまでできたら準備完了、あとは`npm start`コマンドを叩くか、`node 湯婆婆`コマンドを叩けば…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/c0daa5a4-c65f-2090-6ef5-7f2d2137759a.png)

無事に動作しましたね。

### 細かい話

最初に書いたJSとの違いがいくつかあります。

まずはじめに、`window.prompt()`の代わりに`prompts`を利用している点です。
この`prompts`というのは、Node.js用のパッケージ１つで、[npmjs.com](npmjs.com)から`npm`コマンドを経由してダウンロードできます。

Node.jsではプロジェクトごとに使うパッケージをインストールしてから開発を進めます。
その作業が`npm i`というコマンドでした。

ダウンロードし終えたあとは、`import`文を使ってパッケージ内のモジュールを読むことができます[^4]。

[^4]: ESMはモダンブラウザだと動作しますが、Node.jsで`import`文が使えるのは`package.json`に`type: module`が記されているか、拡張子が`.mjs`のもののみです（v14.15.1現在）。それ以外でパッケージを読み込むには`require()`を用います。

```js
import prompts from 'prompts';

const {input} = await prompts({
  type: 'text',
  name: 'input',
  message: '契約書だよ。そこに名前を書きな。',
});
```

`prompts`の振る舞いについては、[promptsの仕様](https://www.npmjs.com/package/prompts)を参照してください。
`window.prompt()`は同期的でしたが、こちらは非同期処理のため`await`キーワードで処理が終わるのを待っています。

後の流れは通常のJavascript同様で、最後の出力が`console.log()`になっています[^5]。

[^5]: `console`には[いろんな種類がある](https://developer.mozilla.org/ja/docs/Web/API/console)のでぜひ試してみてください

```js
console.log(`フン。${input}というのかい。贅沢な名だねぇ。`);
console.log(`今からお前の名前は${moji}だ。いいかい、${moji}だよ。分かったら返事をするんだ、${moji}!!`);
````

これは通常のブラウザで動くJavaScriptでも使うことができますが、通常はエンドユーザの目に触れるところに出力されません。
ブラウザでは開発者ツール[^6]のConsoleタブで確認できます。

[^6]: Google ChromeならF12キーや`Cmd + Option + I`（Mac）、`Ctrl+Shift+I`（Win）で開くことができます


## もっと汎用性を

値の受け取り方はどういう想定なのか？出力方法はどうしたいのか？
ブラウザで動かす想定なのか？Node.jSなのか？CLIで動かすのか？

シーンによって書き方が変化するのは、それはそうって感じですが、このままだとあまりにも汎用性がないですから、機能そのものを切り出してもっと呼び出しやすくしてみます。湯婆婆に汎用性がいるのかどうかは考えてはいけません。

湯婆婆で結局キモなのは

- 受け取った`string`から1文字を返す

この１点に尽きるので、そこを関数にしてしまいましょう。

```js
const 湯婆婆 = input => value[Math.floor(Math.random() * value.length)];
```

…めっちゃ簡素になりましたね。
湯婆婆の本質は、**長さを持つ値から無作為に１つ要素を返却すること**ということがよくわかりました。

あとはこれを他のjsファイルから呼び出せる様に`export`文を書きます[^7]。

[^7]: ESMはモダンブラウザだと動作しますが、Node.jsで`export`文が使えるのは`package.json`に`type: module`が記されているか、拡張子が`.mjs`のもののみです（v14.15.1現在）。それ以外でパッケージを読み込むには`module.exports`を用います。

```js:湯婆婆.js
/**
 * 長さを持つ値から無作為に１つ要素を返す
 * @param {string} 名前 - あなたの名前
 * @returns {string} - あなたの新しい名前
 */
export const 湯婆婆 = 名前 => 名前[Math.floor(Math.random() * 名前.length)];
```

最後に[JSDoc](https://jsdoc.app/)を書いて、**「湯婆婆.js」完成です。**

## いろんな方法で湯婆婆.jsを使ってみよう

### ブラウザで使う

```plaintext:ファイル構成
.
└── 湯婆婆
    ├── index.html
    ├── index.js
    └── 湯婆婆.js
```

#### 最初の方法

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>湯婆婆</title>
<script src="index.js" type="module"></script>
```

```js:index.js
import {湯婆婆} from './湯婆婆.js';

const input = window.prompt('契約書だよ。そこに名前を書きな。');
const moji = 湯婆婆(input);

window.alert(`フン。${input}というのかい。贅沢な名だねぇ。`);
window.alert(`今からお前の名前は${moji}だ。いいかい、${moji}だよ。分かったら返事をするんだ、${moji}!!`);
```

#### DOMから値を読み取って、結果をDOMに表示する

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>湯婆婆</title>
<script src="index.js" type="module"></script>
<style>
body {
  text-align: center;
}
input {
  margin-left: 5px;
}
</style>
</head>
<body>
<main>
<h1>契約書</h1>

<p>契約書だよ。そこに名前を書きな。</p>

<p><label for="name">あなたの名前</label><input type="text" id="name"></p>

<button id="ok">OK</button>

<output id="output"></output>
</main>
</body>
</html>
```

```js:index.js
import {湯婆婆} from './湯婆婆.js';

const textField = document.getElementById('name');
const submitBtn = document.getElementById('ok');
const output = document.getElementById('output');

submitBtn?.addEventListener('click', () => {
  const {value} = textField;
  const word = 湯婆婆(value);

  output.textContent = '';
  output.insertAdjacentHTML('afterbegin', `
    <p>フン。${value}というのかい。贅沢な名だねぇ。</p>
    <p>今からお前の名前は${word}だ。いいかい、${word}だよ。分かったら返事をするんだ、${word}!!</p>
  `);
});
```

### Node.jsで動かす

```plaintext:ファイル構成
.
└── 湯婆婆
    ├── package.json
    ├── index.js
    └── 湯婆婆.js
```

```json:package.json（startコマンドが変わってます）
{
  "type": "module",
  "scripts": {
    "start": "node index"
  },
  "devDependencies": {
    "prompts": "^2.4.0"
  }
}
```

#### 最初の方法

```js:index.js
import prompts from 'prompts';
import {湯婆婆} from './湯婆婆.js';

const {input} = await prompts({
  type: 'text',
  name: 'input',
  message: '契約書だよ。そこに名前を書きな。',
});
const word = 湯婆婆(input);

console.log(`フン。${input}というのかい。贅沢な名だねぇ。`);
console.log(`今からお前の名前は${word}だ。いいかい、${word}だよ。分かったら返事をするんだ、${word}!!`);
```

#### 悪用してArrayを投げてみる

```js:index.js
import {湯婆婆} from './湯婆婆.js';

// 謎の呪文が詰まった配列（Array<string>）を渡してみる
const input = [
  'ガタガタ',
  'でたわね',
  'げぼかわ',
  'きｔら',
  'にーんじん',
  'アーモンド',
];
const word = 湯婆婆(input);

console.log(`フン。${input}というのかい。贅沢な名だねぇ。`);
console.log(`今からお前の名前は${word}だ。いいかい、${word}だよ。分かったら返事をするんだ、${word}!!`);
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/1e5c649f-2606-bf4f-dd7f-f929b768577d.png)

残念、JavaScriptらしく無事に動いてしまいました。


## お客様とて許せぬ！

文字列から1文字だけを抽出する前提の湯婆婆。
配列を渡してくるのはやめていただきたいですね。

さて、JavaScriptの力を駆使して配列が飛んできたら例外を投げる処理を組み込んでやりましょう。

```js:湯婆婆.js
/**
 * 長さを持つ値から無作為に１つ要素を返す
 * @param {string} 名前 - あなたの名前
 * @returns {string} - あなたの新しい名前
 */
export const 湯婆婆 = 名前 => {
  if (typeof 名前 === 'string') {
    return 名前[Math.floor(Math.random() * 名前.length)];
  }

  throw new TypeError('お客様とて許せぬ！');
}
```

これでどうでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/9cfb7417-d28f-4984-b468-71b72b23bc0b.png)

はい、無事に湯婆婆は怒り狂ってクラッシュすることができました。

以上、湯婆婆.jsでした！

## でもそれ、めんどくさくない？

いちいち受け取った値が正しいか検証するコードを書くなんて、Javascriptで使うライブラリやプラグインを作る意外では不毛な時間です。
引数や返り値の型が合っているか？なんてことはもう人間が考える必要はありません[^8]。

[^8]: 語弊がありますが、多くの場合はTSの型推論とチェックにまかせていればそこまで神経質になる必要はない気がしています。

JavaScriptとほとんど同じ記法で書けるTypeScriptを使いましょう。

まずは次のコマンドを実行します。

```
npm i -D typescript ts-node
```

tsconfig.jsonを作ります[^9]。

[^9]: この設定は適当です。とりあえず動かすための設定なので、実際にプロジェクトでTypeScriptを採用するときはよく考えて設定してください。他にもたくさんの設定項目があります。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "commonjs",
    "strict": true,
  },
  "include": [
    "./**/*.ts"
  ]
}
```

package.jsonのコマンドを書き換えます。

```json:package.json
{
  "scripts": {
    "start": "ts-node index",
  },
  "devDependencies": {
    "prompts": "^2.4.0",
    "ts-node": "^9.1.1",
    "typescript": "^4.1.2"
  }
}
```

湯婆婆.jsは例外処理を書く前の状態にもどして、jsファイルの拡張子を2つともtsに書き換えます。

```js:湯婆婆.ts
/**
 * 長さを持つ値から無作為に１つ要素を返す
 * @param {string} 名前 - あなたの名前
 * @returns {string} - あなたの新しい名前
 */
export const 湯婆婆 = 名前 => 名前[Math.floor(Math.random() * 名前.length)];
```

```js:index.ts
import {湯婆婆} from './湯婆婆';

const input = [
  'ガタガタ',
  'でたわね',
  'げぼかわ',
  'きｔら',
  'にーんじん',
  'アーモンド',
];
const word = 湯婆婆(input);

console.log(`フン。${input}というのかい。贅沢な名だねぇ。`);
console.log(`今からお前の名前は${word}だ。いいかい、${word}だよ。分かったら返事をするんだ、${word}!!`);
```

最終的なフォルダ構成はこちら。

```plaintext:ファイル構成
.
└── 湯婆婆
    ├── package.json
    ├── tsconfig.json
    ├── index.ts
    └── 湯婆婆.ts
```

このまま、次のコマンドを叩くとどうなるでしょうか。

```
npm start
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/4d00422c-21dc-9599-f608-de8320a4c070.png)

なんだかさっきと違うエラーがでましたね。

> Parameter '名前' implicitly has an 'any' type.

これは「`湯婆婆()`が受け取る関数の型がかかれてないぞ！」というエラーです。

TypeScriptは、どういう値を受け取るのかをあらかじめコードに書いておいて、コードをJavaScriptに変換してから実行するときに処理が合ってるか検査してくれます。
さっそく型を書いてみましょう。

```typescript:湯婆婆.ts
/**
 * 長さを持つ値から無作為に１つ要素を返す
 * @param 名前 - あなたの名前
 * @returns あなたの新しい名前
 */
export const 湯婆婆 = (名前: string) => 名前[Math.floor(Math.random() * 名前.length)];
// export const 湯婆婆 = (名前: string): string => 名前[Math.floor(Math.random() * 名前.length)]; 返り値も`string`だと明示するとこう。でもTypeScriptくんは賢いので書かなくてもわかるとこは書かなくてもいい
```

ついでに、JSDocも[TSDoc](https://tsdoc.org/)に変えてみました。これでどうでしょうか。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/96fc796e-24d2-1fac-adf9-ab7800e231e9.png)

今度は別のことを怒られていますが、

>  Argument of type 'string[]' is not assignable to parameter of type 'string'.

これは`string`が渡されるはずなのに`string[]`（文字列だけが入った配列）が渡されてるぞ！約束と違う！と怒っているようです。

では、渡す値を`string`にしてみましょう。

```typescript:index.ts
import {湯婆婆} from './湯婆婆';

const input = '桐生ココ';
const word = 湯婆婆(input);

console.log(`フン。${input}というのかい。贅沢な名だねぇ。`);
console.log(`今からお前の名前は${word}だ。いいかい、${word}だよ。分かったら返事をするんだ、${word}!!`);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/0e072b67-34ee-7e33-ccc5-6189c0a62ec9.png)

無事に動作しました！[^10]

[^10]: TypeScript対応していないパッケージは読めなかったり、動かすためには型ファイルもインストールしなかったりいけないケースもあるので注意が必要です。今回の例ではESM式のモジュールの読み込みをしていますが、TypeScriptかどうかにかかわらずともESMに対応してないパッケージをCommonJS方式で読み込むことや、その逆をしようとすると動作しなかったりするので、いきなり導入に踏み切るよりその辺りも含めてある程度理解してからプロジェクトに持ち込むほうが安全です。

このように、自分が作った関数が正しく使われているかをいちいち気にしなくてよくなる点もTypeScriptの魅力ですが、記法が純粋なJavaScriptに追記するだけでというのも大きな魅力の一つです。JavaScriptが書ければとても入って行きやすい世界だと思います。

そうして規模が大きくなってくると型の受け渡しだけでなく、その関数や機能の処理自体が変わらないかをテストしたくなってきます。
自分が触った影響で他が壊れるかもしれない、という環境で作業をするのは精神的にもよくないので、人々はmocha, chai, jest, karmaなどなどキーワードにたどり着いてうんたらかんたら。

## さいごに

なんだかTypeScriptの斡旋みたいな終わり方になってしまいましたが、TypeScriptにはちょびっと触れた程度なので、気になった方はぜひ調べてみてください。
TypeScriptは基本は最終的にJavaScriptに変換して使うものなので、

- tsconfig.jsonの設定
- [ESlint](https://eslint.org/)の設定
- 変換する方法（`tsc`コマンド）
- TypeScriptでどこまでチェックするのか
- jsファイルに変換したあとは[Babel](https://babeljs.io/)を通すのか
- モジュール同士は[webpack](https://webpack.js.org/)や[Snowpack](https://www.snowpack.dev/)でまとめるのか

などなどのいろんな課題が立ちはだかってきますが、それは世の中に死ぬほど良記事がありますので、ぜひ探してみていただければと思います。

TypeScriptで書く意味あるの？と思っているみなさん、
JavaScriptでの湯婆婆実装はとてもコンパクトで簡単な話でしたが、それでも型チェックの必要性がすこしは見えたのではないでしょうか？

TypeScript基本だろｊｋのみなさん、マサカリは入り口においておいてもろて…＞＜

結論、TSにしろJSにしろ、ECMAScript関連は書いててとても楽しいです。生まれてきてくれてありがとう。

そして、10日公開だったのに遅れてごめんなさい（現在11日午前３時４０分）。

JavaScriptを知らない多言語プログラマさん向けに書こうと思ってたのですが、
書いてるあいだにわけわかんなくなってしまった感があります🙈

それでもここまで読んでくださった皆さん、ありがとうございました！！