JavaScript/TypeScriptメモ

functionは第一級オブジェクト - most adequate guide Chapter 2のテキトーな訳

このページの趣旨

下記ページの、かなり「大雑把」に、「私が興味を持ったところだけ」を「雰囲気」で翻訳したものです。

mostly-adequate.gitbooks.io Chapter 02: First Class Functions

原文をかなり崩して書いている(原文にない単語、文章を追加したりしている箇所もある)ので、翻訳とすら、呼べないものかもしれません。

違和感を感じたら、原文を参照することをおすすめします。

Chapter2: functionは第一級オブジェクト

概要

functionが「第一級(first class)」と呼ばれるのは、functionが他の「もの」とまったく同じときです。言い換えると「普通のclass」ということです。関数を他のデータと同じく扱うことができ、かつ、かつ、関数は、他のデータと変わるところはありません。関数は、配列に格納したり、関数の引数として指定したり、変数に代入することができます。

このことは、JavaScriptの基本的な事項ですが、再確認してください。なぜなら、githubでちょっとコードを検索すると、このことを無視したプログラムが大量に出てくるからです。例えば、次の例を見てみてください。

const hi = name => `Hi ${name}`;
const greeting = name => hi(name);

ここで、h1に対して作成したラッパーgreetingは、完全に冗長なものになっています。なぜでしょうか?それは、JavaScriptでは、functionは「呼び出し可能(callable)」だからです。hi()を付けて呼び出せば、functionとして実行されて値が返されます。もし()を付けなければ、単に、変数に格納されたfunction(を定義するソースコード)が返されます。確認のために、あなた自身で確かめてみてください。

hi; // name => `Hi ${name}`
hi("jonas"); // "Hi jonas"

greetingは、単にh1を呼び出しているだけで、引数もまったく同じなので、次のように簡潔に書くことができます。

const greeting = hi;
greeting("times"); // "Hi times"

言い換えると、hiは、元から1つの引数を受け取るfunctionなのに、なぜ、同じ引数で単にh1を呼び出すだけのfunctionをわざわざ別に作る必要があるのでしょうか?まったく意味がありません。

ムダに冗長なだけでなく、たまたま、内部で呼び出している関数を遅延実行できるようになりましたが、遅延実行をする書き方としても良い書き方ではありません(その理由は、後述するように、内部の関数の仕様変更に伴いこの関数を書き替える必要があるからです)。

npm packagesにも出てくる冗長な書き方の例

この問題については、次に進む前に、きちんと理解しておいたほうがいいでしょう。そこで、もう少し、面白い例を見てみましょう。npm packagesのライブラリには、次のようなものがあります。

// 冗長な例
const getServerStuff = callback => ajaxCall(json => callback(json));

// 書き換え後
const getServerStuff = ajaxCall;

このような書き方をしているajaxのコードは、様々な場所で出てきます。

実際、この2つは、次のようにして等価であることを確かめられます。

// この部分は、、
ajaxCall(json => callback(json));

// 下記と同じ
ajaxCall(callback);

// だから、getServerStuffを、次のようにリファクタリングすることができて
const getServerStuff = callback => ajaxCall(callback);

// これは、下記と同じになる
const getServerStuff = ajaxCall; // <-- ()がなくなりました

たとえば、もう1つの例を見てみましょう。それをみると、なぜ、私がここまでしつこくこの話を言うかがわかるでしょう。

const BlogController = {
  index(posts) { return Views.index(posts); },
  show(post) { return Views.show(post); },
  create(attrs) { return Db.create(attrs); },
  update(post, attrs) { return Db.update(post, attrs); },
  destroy(post) { return Db.destroy(post); },
};

このバカげたControllerは99%意味がありません。このプログラムは、次のように書き換えられます。

const BlogController = {
  index: Views.index,
  show: Views.show,
  create: Db.create,
  update: Db.update,
  destroy: Db.destroy,
};

あるいは、単にViewとDBをひとつにまとめているだけなのですから、この文自体も不要で、バラバラにしてもいいかもしれません。

なぜ「第一級(first class)」が良いのか

なぜ、functionが「第一級(first class)」として扱われるのが良いのか、もう少し考えてみましょう。

先ほど見たgetServerStuffBlogControllerの例のように、簡単に、なんの付加価値も生み出さない、メンテナンス・検索性を損なうだけのムダなコードを増やすことができます。

さらに、このように無意味にラップされた関数を修正するときに、ラッパー関数も修正する必要が出てきます。たとえば、次の例を見てみましょう。

httpGet('/post/2', json => renderPost(json));

ここで、httpGetを、errが起きた場合にそれをcallback関数に返すように修正してみましょう。すると、次のように、ラッパー関数も修正する必要がでてきます。

// httpGetを呼び出しているすべての箇所で、引数にerrを付け加える必要がある
httpGet('/post/2', (json, err) => renderPost(json, err));

もし、functionを「第一級(first class)」として書いていれば、修正すべき箇所は大きく減るでしょう。

// httpGetは、renderPostを好きな引数で呼び出すことができます
httpGet('/post/2', renderPost);

引数の名前を一般化する

不要なfunctionを取り除くだけでなく、引数の名前も取り除くべきです。というのは、引数の名前は、実体とずれてしまう場合があるのです。特に、プログラムを作ってから時間が経ったり、要件が変わったときに問題になります。

ですから、引数の名前は取り除きましょう。同じものに対して複数の名前が付いていると、プロジェクトが混乱する原因となります。

また、変数名は、汎用性にも関わってきます。たとえば、次の2つのfunctionはまったく同じことをしていますが、後者のほうが、より一般的で、再利用できるように感じます。

// 現在のブログに特化していている
const validArticles = articles =>
  articles.filter(article => article !== null && article !== undefined),

// 将来のプロジェクトにも使いまわせそう
const compact = xs => xs.filter(x => x !== null && x !== undefined);

状況に特化した名前を付けてしまうと、私たち自身、そのデータ(たとえば、今の例であればarticles)と結びつけて考えてしまいがちです。これは、再発明の原因になってしまいます。

thisを使うのはトラブルの元

また、もう1つ伝えるべきことがあります。オブジェクト指向で書くコードのように、thisを使うと、後で、大きな問題になりがちです。もし、下層のfunctionでthisをつかっている場合、そのfunctionの引数として「第一級(first class)のfunction」を指定すると、抽象化が破れてしまいます。

const fs = require('fs');

// NG
fs.readFile('freaky_friday.txt', Db.save);

// OK
fs.readFile('freaky_friday.txt', Db.save.bind(Db));

自分自身をbindしておくことで、DBは、自分自身のプロトタイプのコードに無理やりアクセスすることはできます。ただ、thisの使用は避けるべきでしょう。実際、関数型の作法でコードを書いていれば、thisを使う必要はありません。とはいえ、他のライブラリを使う場合には、このような面倒なことを意識しないといけないかもしれません。

thisは、実行速度を上げるために必要だ、という人もいるかもしれません。もし、あなたが実行速度に過度にこだわるのであれば、この本を読むのは止めましょう。実行速度にこだわる代わりに、手間がかかることは覚悟しましょう。

それでは、次の章に進みましょう。