🍙

クラサバアプリ脳で Webアプリを学ぶ(vue + python) 40代後半 のメモ その4 JavaScript関数その2

2022/06/09に公開約4,000字

第一級オブジェクト とは何者か??

JavaScript における「関数は第一級オブジェクトである」とはなんぞや?

オブジェクト指向プログラミングの説明で良く出てくる「用語」に対してもそうなんですが、「継承」とか「移譲」とか、ロールプレイングゲームの伝説の武器や魔法の「伝説の武器(魔法)の伝授」みたいなイメージしか持てないオタク脳な自分が居ます・・。

そんなことも言ってられないので、正しい用語の意味を確認するため、例によって
Software Design2022 年 01 月号の特集

https://gihyo.jp/magazine/SD/archive/2022/202202
の力を借りますと、

・第一級オブジェクト(first-class object)
式の中に現れたり変数に代入したり関数の引数に渡したりできるもの
とあります。つまり、関数を普通の変数のような形で扱うことができる、と捉えて良いのでしょうか?
変数は、自身の今までの理解では 値を取っておく入れ物 というイメージです。
よって、使い方としては、関数の引数に変数を定義して、その変数に関数を代入して、関数に渡して、って書いてるだけでなんだか訳がわからなくなってきました。

mdn 公式ドキュメントで整理しよう

mdn ドキュメントを参照します。

https://developer.mozilla.org/ja/docs/Glossary/First-class_Function
第一級オブジェクト(first-class object)ではなく、第一級関数(first-class function)ですが、意味合いは同じと捉えました。
このサンプルの中で、引数として関数を渡す のサンプルコードが以下ですが
function sayHello() {
  return "Hello, ";
}
function greeting(helloMessage, name) {
  console.log(helloMessage() + name);
}
// `sayHello` を `greeting` 関数の引数として渡す
greeting(sayHello, "JavaScript!");

・sayHello は、「"Hello,"という文字列」を戻す。
・greeting は、「helloMessage」「name」を受け取り、console.log にて、helloMessage()関数という名の sayHello 関数を呼び出し、name に代入されたパラメータ文字列を連結する。

という動作を行うはず、ですが、
ここでのこのコードの、自身の理解のしにくさは、なんといっても

greeting 関数の第1引数パラメータの「型指定がない」ので、どんな動きをするのかがパッとみて良くわからない

が正直な感想です。つまりは、「関数を渡すことができる」という前提条件が理解できていないのですね。

C# のラムダ式を確認してみよう

プログラミングの解説書籍の著者で、私が尊敬する 出井秀行さんの 実戦で役立つ C#プログラミングのイディオム/定石&パターンの書籍内記述内容を参考に、「メソッドの引数にメソッドを渡す」を考えてみます。

まず、C#での「メソッド」は、クラス内の振る舞いを表すものとして定義されるとあります。
ラムダ式は、もともとはデリゲートや匿名メソッド(無名メソッド)を利用し、メソッドの引数にセットする形で特定の振る舞いを持たせていたものを、さらに簡略化(型推論にて型宣言を省略、かつ、単一引数であればカッコも省略)し、処理記述を1行(コンパクト)にまとめている中で、「式」として定義します。
処理内容を1行に集約させることで、とあるループ内で別のループを書くことや、if 文記述を減らすことができますね。式と扱われることで、単純ループからの1行記述の呼び出しで複雑な処理を構築できるようになるのも大きなメリットと捉えました。

出井さんの書籍内では、ラムダ式を活用し、メソッドの機能をアップ(できることが増える)させることで、

How(どうやるか)ではなく、What(何をやるか)で記述することで、「抽象度が上がる」

と説明があります。抽象度が上がれば、汎用性も上がる。関数を再利用することを考える側面においても、とてもいいことだと思います。

しかし、C#のラムダ式も型推論で引数の形は定義していないな(ぱっと見は同じ形)。
だからアロー演算子やアロー関数を利用することで「関数式である」と理解できる形で記述をするものだと認識しました。

JavaScript 関数も、よく見ると 「第一級オブジェクト」で、考え方は同じかも

JavaScript のオブジェクト(第一級オブジェクトのオブジェクトとは別物だそうです)は、「値の一種」。キー情報と値が関連して成立します。
mdn ドキュメントにもあるように

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects
「オブジェクトは、プロパティの集まりであり、プロパティは名前 (あるいはキー) と値との関連付けから成り立っています」
とあります。プロパティに値が入るので、当然、プロパティに関数が定義できることになりますね。
うわー、この時点で混沌としてきたぞ・・(プロパティは属性にすぎないのではないのか?と・・)。

さらに mdn の力を借りると

https://developer.mozilla.org/ja/docs/Glossary/Method

メモ: JavaScript では、関数自身はオブジェクトです。そういう意味では、メソッドは実際には関数へのオブジェクト参照です。

もはや、何が関数で何がメソッドで何が式なのかがぐちゃぐちゃになってきました。

整理すると
・JavaScript ではオブジェクトは値。オブジェクト以外の値はプリミティブ(メソッドを持たないデータ(値))と呼ぶ
・関数とメソッドは(厳密には)イコールではない。
・JavaScript のメソッドは、「オブジェクトのプロパティで関数」で厳密には「関数へのオブジェクト参照」
・JavaScript のプロパティは、「名前 (あるいはキー) と値との関連付け」
・プロパティに値が入れられるので、関数も入れられる。
・JavaScript でのオブジェクトは連想配列のようなものとみなせる。

const hoge = {
  name: "okojyo",
  age: 48,
};
console.log(hoge.age); // => 48

上記の例では、プロパティは name と age ですね。

そして、関数をプロパティに入れて使う場合は

const propKansu = {
  kakeru: function(x, y){ return x * y};
};
console.log(propKansu.kakeru(10,5)); // => 50

命名規約としてはだめな例かも(あえてローマ字で表現しています)ですが、データのかたまりをクラスで定義し、振る舞いと属性を決める、という C#の考え方と同じ形で定義されているとイメージできます。
(微妙に違いますが・・)

また、Software Design2022 年 01 月号の特集では、即時関数を利用する場合と、利用しない場合を例として、前後の処理の変数スコープを制限する形、つまり、「関数スコープを明確にする」ことで、「即時関数として利用するかしないかで、必要な処理のためだけに変数を用意している(スコープ外では使われない)」という意図を明確にできるとあります。

しかし、VB(.Net でもない) に慣れたおっさんは思うのです。
「明確に宣言されていない関数が、その場でどんな機能を持つかを正確に読み解かないと、引数に渡されたのち、どのように処理されるかが分かりにくいのでは?」
と・・・(右クリックして、定義、とかで宣言に飛んでいきたいのです・・。)

ただし、この考え方を壊し、高機能なメソッド(関数)を短くシンプルに表現することに頭を切り替える必要があることを強く感じています。

また、別の JavaScript 関数概念として「高階関数」とか「コールバック」といった、私が JavaScript を触れるのに一番分かりにくいな と、感じている概念があります。
その理解についての記述は別の機会に語るとして、まずは、

JavaScript の関数は、第一級オブジェクトとして、関数を多段で構えることで、アロー関数式などを用いて関数スコープを明確にし、複雑な処理をシンプルに記述することを可能にするもの

と捉えるようにします。
どうしてこのようなスタイルになったのかは、JavaScript の今までの歴史や、利用方法に対する機能拡張の歴史である側面も併せて理解していきたいところです。

ぐむー、やっぱり C#ももう一回勉強しなおそう。
JavaScript と C# のような 静的型付け言語との比較は面白いですね。
(これらの機能の実装目的は、大体同じゴールを目指していると思われるからです。)

GitHubで編集を提案

Discussion

ログインするとコメントできます