関数型のよさがわかる実践例

# はじめに
手続き型と比べて関数型のよさを布教する人が多いけど、100までの偶数の和を足す例で説明されても正直腑に落ちないわけで。もう少し複雑というか、現実に即した例で説明したほうがいいんじゃない？

# お題

- 仕様
 - EUの中央銀行 ECB が提供する為替データをもとに、
 - USD/JPY, EUR/USD, HKD/USD の値を出力するコンソールプログラムを作る

- 制約
 - JavaScript で実装 
 - 取得データは http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml
 - XMLパーサには xml2js を使う
 - HTTPリクエストには axios を使う
 - linter に怒られないコード

- 出力例

```
$ node forex.js
{ JPYUSD: 107.01399563142141,
  HKDUSD: 7.849688536526171,
  EURUSD: 1.2361 }
```

# 回答例1：そこそこ頑張った

自分で実装してから、回答例を見てほしい。

**一つ目の回答例は全体で23行、一時変数はたったの 2個。**あなたのコードはどうだっただろう。コメントで教えてくれてもいいのよ。

```js
const { promisify } = require('util');
const axios = require('axios');
const xml2js = require('xml2js');
const R = require('ramda');

const xmlToJSON = promisify((new xml2js.Parser()).parseString);
const createRawExchangeTable = R.pipe(
  R.path(['gesmes:Envelope', 'Cube', 0, 'Cube', 0, 'Cube']),
  R.map(R.prop('$')),
  R.reduce((acc, x) => { acc[x.currency] = parseFloat(x.rate); return acc; }, {}),
);
const createExchangeTable = table => ({
  JPYUSD: table.JPY / table.USD,
  HKDUSD: table.HKD / table.USD,
  EURUSD: table.USD,
});
const getRate = async () => {
  const response = await axios.get('http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml');
  const jsonData = await xmlToJSON(response.data);
  return R.pipe(createRawExchangeTable, createExchangeTable)(jsonData);
};

getRate().then(console.log);
```

＃つーか、`xml2js` の吐くJSONが汚すぎる、、、

# 回答例2: モナドを使うとすごくいい
Promise はモナドじゃないのでポイントフリーにできない。Promise の代わりに、folktale の `Task`モナドにすると、**回答例は全体で24行、一時変数は 0個になる。しかもインデントの深さは最大でも1**。

```js
const axios = require('axios');
const xml2js = require('xml2js');
const R = require('ramda');
const nodebackFromTask = require('folktale/conversions/nodeback-to-task');
const promisedToTask = require('folktale/conversions/promised-to-task');

const xmlToJSON = nodebackFromTask((new xml2js.Parser()).parseString);
const createRawExchangeTable = R.pipe(
  R.path(['gesmes:Envelope', 'Cube', 0, 'Cube', 0, 'Cube']),
  R.map(R.prop('$')),
  R.reduce((acc, x) => { acc[x.currency] = parseFloat(x.rate); return acc; }, {}),
);
const createExchangeTable = table => ({
  JPYUSD: table.JPY / table.USD,
  HKDUSD: table.HKD / table.USD,
  EURUSD: table.USD,
});
const getRate = R.compose(
  R.map(createExchangeTable),
  R.map(createRawExchangeTable),
  R.chain(xmlToJSON),
  R.map(R.prop('data')),
  promisedToTask(() => axios.get('http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml')),
);
getRate().run().promise().then(console.log);
```

# 回答例3: 普通に手続き的に
普通に書くと、**28行で一時変数は5個。**

```js
const { promisify } = require('util');
const axios = require('axios');
const xml2js = require('xml2js');

const xmlToJSON = promisify((new xml2js.Parser()).parseString);
const createRawExchangeTable = (obj) => {
  const dataPart = obj['gesmes:Envelope'].Cube[0].Cube[0].Cube;
  const table = {};
  for (x of dataPart) {
    table[x.$.currency] = parseFloat(x.$.rate)
  }
  return table;
};
const createExchangeTable = (jsonData) => {
  const table = createRawExchangeTable(jsonData);
  return {
    JPYUSD: table.JPY / table.USD,
    HKDUSD: table.HKD / table.USD,
    EURUSD: table.USD,
  };
};
const getRate = async () => {
  const response = await axios.get('http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml');
  const jsonData = await xmlToJSON(response.data);
  return createExchangeTable(jsonData);
};

getRate().then(console.log);
```

# 考察

- 関数型にすると、一時変数の数は大幅に削減できる
    - データではなく、操作にフォーカスできるのは強み。
- 関数型にすると、for文 はなくせる
    - 代わりに reduce, map になるので読みやすくなるかは微妙。
    - バグを減らせるか？はあまり期待できないかも。
- モナドまで手を出すと、関数型はがぜん読みやすくなる
    - ポイントフリー（完全に composable）につくると読みやすい
    - そうでない場合、手続き的な部分と関数型てきな部分が混在するので、人によっては読みにくいとか、手続き型とそんなに変わりがないと思ってしまうかも。

今度ポエムがバズったら、この記事を紹介してみよう。

# おまけ：for 文は読みにくい？
全く同等のことを for と reduce で書いた例が以下（さっきの例から抜粋）。どっちがよみやすい？まあ、どっちもどっちじゃない？

```js
const createRawExchangeTable = R.pipe(
  R.path(['gesmes:Envelope', 'Cube', 0, 'Cube', 0, 'Cube']),
  R.map(R.prop('$')),
  R.reduce((acc, x) => { acc[x.currency] = parseFloat(x.rate); return acc; }, {}),
);

const createRawExchangeTable2 = (obj) => {
  const dataPart = obj['gesmes:Envelope'].Cube[0].Cube[0].Cube;
  const table = {};
  for (x of dataPart) {
    table[x.$.currency] = parseFloat(x.$.rate)
  }
  return table;
};
```