この記事は、記憶を深め、後で復習できるように整理して記録するために、関数式の学習中に理解した事柄の一部を記録します。
私は最近予約販売された「JavaScript 関数型プログラミング」という本を見て決心しました。主な目的は、関数型プログラミングが何なのかまだ理解していないことです。私自身が学習する過程で、周りの人がプロセス指向プログラミングやオブジェクト指向プログラミングについて話しているのを常に聞いてきましたが、関数型プログラミングはほとんどありません。他の学生に遅れをとらないように、読書から得た知識をノートに書いて共有し、記録したいと思いました。
js と関数型プログラミング
この本では、関数型プログラミングとは何かを簡単な文で答えています。
関数型プログラミングでは、関数を使用して値を抽象単位に変換し、それを使用してソフトウェア システムを構築します。
この文章を読んでも、関数型プログラミングとは何か、なぜ関数型プログラミングを使う必要があるのか、まだよく分からない学生もいると思います。次の例の多くではアンダースコアが使用されています。
関数を抽象単位として使用する
抽象メソッドは、詳細を隠す関数を指します。この本から、出力年齢値を検出する関数 (主にエラーと警告についてレポートする) を例に挙げます。
function parseAge(age) { if (!_.isString(age)) throw new Error("Expecting a string"); var a; console.log("Attempting to parse an age"); a = parseInt(age, 10); if (_.isNaN(a)) { console.log(["Could not parse age: "].join()); a = 0; } return a; }
上記の関数は、年齢が入力されたかどうかを判断します。年齢は文字列形式である必要があります。次のステップは、この関数を実行することです:
parseAge("42"); //=> 42 parseAge(42); //=> Error:Expecting a string parseAge("hhhh"); //=> 0
上記の parseAge 関数は問題なく正常に動作します。出力エラー=情報と警告の表示方法を変更したい場合は、対応するコード行と他の場所の出力モードを変更する必要があります。この本で説明されている方法は、それらをさまざまな関数に抽象化することで実現されます。
function fail(thing) { throw new Error(thing); } function warn(thing) { console.log(["WARNING:", thing].join('')); } function note(thing) { console.log(["NOTE:", thing].join('')); }
次に、上記の関数を使用して parseAge 関数を再構築します。
funciton parseAge(age) { if (!_.isString(age)) fail("Expecting a string"); var a; note("Attempting to parse an age"); a = parseInt(age, 10); if (_.isNaN(a)) { warn(["Could not parse age:", age].join("")); a = 0; } return a; }
エラーを報告するコードを別の関数に入れると、リファクタリングされた parseAge は以前のものからあまり変わりません。ただし、違いは、エラー、メッセージ、警告を報告するという概念が抽象化されていることです。エラー、メッセージ、警告のレポートも完全に刷新されました。
これは、動作が単一の関数に含まれているため、その関数を同様の動作を提供する新しい関数で置き換えたり、まったく異なる動作で直接置き換えたりすることができます。
カプセル化と隠蔽
このタイトルは分かりやすいので、例を示します。私たちは地球規模の汚染を避けるためによく iife を使用します。これはカプセル化と隠蔽の良い例です。 iife を使用して作成した変数やメソッドの一部を非表示にすることで、地球環境を汚染することが目的ではありません。これもクロージャを使用してデータを非表示にします。
クロージャーも関数だからです。そして、それは今関数型プログラミングを学ぶことと大きく関係しています。ただし、以前に学んだオブジェクト指向のカプセル化を忘れないでください。結局のところ、どちらが優れているとは言えません。しかし、それらをすべてマスターすれば、悪いことではありません。古いことわざにあるように、需要に注目してください。
動作の単位として関数を使用する
データと動作を非表示にすること (通常、簡単な変更には不便です) は、関数を抽象的な単位として説明する方法にすぎません。もう 1 つのアプローチは、基本的な動作を保存してオフラインの個別ユニットに転送する簡単な方法を提供することです。
本の中の小さな栗、js 構文を使用して配列内の値にインデックスを付ける:
var arr = ['a', 'b', 'c']; arr[1] //=> b
上記の配列内の値にインデックスを付けるのは簡単ですが、関数に入れずにこの動作を取得し、必要に応じて使用する方法はありません。配列内の値にインデックスを付ける単純な関数 nth を作成します:
function nth(a, index) { return a[index]; }
次に、次を実行します:
nth(arr, 1) //=> b
操作は成功しますが、空のオブジェクトが渡されるとエラーが報告されます。したがって、nth に関する関数の抽象化を実装したい場合は、次のステートメントを設計できます。nth は、インデックス アクセスを許可するデータ型に格納された有効な関数を返します。このステートメントの鍵となるのは、インデックス データの種類の概念です。おそらく、タイプを決定する関数が必要です:
function isIndexed(data) { return _.isArray(data) || _.isString(data); }
次に、n 番目の関数の改良を続けます。 isIndexed 関数は、特定のデータが文字列であるか配列であるかを判断するための抽象化を提供する抽象化です。
function nth(a, index) { if (!_.isNumber(index)) fail("Expected a number as the index"); if (!isIndexed(a)) fail("Not supported on non-indexed type"); if ((index < 0) || (index > a.length - 1)) fail("Index value is out of bounds"); return a[index]; }
インデックスからオブジェクトを抽出して n 番目の関数抽象化を構築するのと同じ方法で、2 番目の抽象化を構築することもできます。
function second(a) { return nth(a, 1); }
函数second允许在一个不同但相关的情况下,正确的使用nth函数:
second(arr); //=> b
通过上面的栗子,就知道。我们可以把每一步都抽象成一个函数,把每一个参数都抽象出来。虽然这样写感觉定义了许多函数。不过这样更加容易理解每一项的功能和流程。
数据抽象
JavaScript 的对象原型模型是一个丰富且基础的数据方案。
因为js没有类的原因,就有了许多模拟类的方法,且在ES6上也出现了class关键字。尽管类有许多长处,但很多的时候js应用程序的数据需求币类中的简单的要多。
基于类的对象系统的一个有理的论据是实现用户界面的历史使用。
js中的对象和数组已经能够满足我们对数据的操作了,且Underscore也是重点也是如何处理数组和对象。
实施和使用的简易性是使用js的核心数据结构进行数据建模的目的。这并不是说面向对象或者基于类的方法就完全没有用。处理集合为中心的函数式方式更加适合处理与人有关的数据,而面向对象的方法最适合模拟人。
js函数式初试
在开始函数式编程前,需要先定义两个常用且有用的函数:
function existy(x) { return x != null } function truthy(x) { return (x !== false) && existy(x); }
existy函数旨在定义事物之前的存在。js中就有两个值可以表示不存在:null和undefined。
truthy函数用来判断一个对象是否应该认为是true的同义词。
我们可以在很多地方使用到这两个函数,其实函数式理念来自于它们的使用。有些同学可能已经熟悉了许多js实现中的map forEach等方法。且Underscroe也提供了许多类似的方法,这也许就是选择Underscroe来辅助学习函数式编程的原因。
简单说下就是:
一个对”存在“的抽象函数的定义。
一个建立在存在函数之上的,对”真“的抽象函数定义。
通过其他函数来使用上面的两个函数,以实现更多的行为。
加速
大概了解了函数式编程之后。你可能会想这函数式编程不是很慢吗?比如前面获取数组索引,有必要定义一个函数来专门获取吗?直接用arr[index]绝对比那些函数来的快。
var arr = [1, 2, 3, 4, 5]; // 最快 for (var i = 0; i < arr.length; i++) { console.log(arr[i]); } // 较慢 _.each(arr, function (val, index) { console.log(index); });
但是我们在写代码的时候可能不会考虑的那么深,也许使用函数的确比原生要慢一些。但是大多数情况下也不会去在乎那么点时间,且现在有强大的v8引擎,大部分情况下的他都能很高效的编译和执行我们的js代码。所以我们没有必要在还没有写出正确的代码前考虑运算速度。
如果是我来选择的话,可能会更加关注与代码的风格。那种写法写的舒服看的舒服就使用哪一种,当然也是要保证基本的运算速度下,以不至于慢的离谱。看的舒服的代码比跑的快的代码可能更加有成就感。
总结
看完了第一章也是可以小结一下js的函数式编程。下面引用书上的总结:
确定抽象,并为其构建函数。
利用已有的函数来构建更加复杂的抽象。
通过将现有的函数传给其他的函数来构建更加复杂的抽象。
单是构建抽象还是不够的,如果能够把强大的数据抽象结合来实现函数式编程效果会更加好。
后面的章节读后感会慢慢的分享给大家,敬请关注。