ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript 関数プログラミング (1)

JavaScript 関数プログラミング (1)

黄舟
黄舟オリジナル
2017-03-06 14:14:211003ブラウズ

1. はじめに

関数型プログラミングというと、抽象的で理解できない記号がたくさん詰まった、大学の教授だけが使用しているような、あいまいな学術コードが第一印象になるかもしれません。これらの事。ある時代にはそうであったかもしれませんが、近年ではテクノロジーの発展に伴い、関数型プログラミングが実際の制作において大きな役割を果たしており、クロージャや匿名関数、関数型プログラミングの典型的な特徴 関数型プログラミングは、ある程度、命令型プログラミングを「同化」させています。

過去 2 年間の React の人気に伴い、関数型プログラミングの概念も普及しており、その他のオープンソース ライブラリでも関数型プログラミングの特性が使用されています。 。それでは、関数型プログラミングの知識と概念を紹介しましょう。

2. 純粋関数

中学生の数学の知識をまだ覚えている方にとって、関数 f の概念は、入力 x に対して出力 y = f(x) を生成するというものです。これは最も単純な純粋関数です。 純粋関数の定義は、同じ入力に対して、目に見える副作用や外部環境の状態に依存することなく、常に同じ出力を取得することです。

たとえば、JavaScript では、配列に対する操作には純粋なものとそうでないものがあります。

var arr = [1,2,3,4,5];

// Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
// 可以,这很函数式
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]

// Array.splice是不纯的,它有副作用,对于固定的输入,输出不是固定的
// 这不函数式
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []

関数型プログラミングでは、必要なのはスライスのような純粋な関数であり、スプライスのような関数ではありません。呼び出されるたびにデータが台無しになります。

なぜ関数型プログラミングは不純な関数を除外するのでしょうか?別の例を見てみましょう:

//不纯的
var min = 18;
var checkage = age => age > min;

//纯的,这很函数式
var checkage = age => age > 18;


不純なバージョンでは、 checkage この関数の動作は、入力パラメータの age に依存するだけでなく、外部変数 min にも依存します。関数の動作は、外部システム環境によって決定される必要があります。大規模システムの場合、この外部状態への依存が、システムの複雑さを大幅に増大させる主な理由です。

純粋な checkage は関数内のキー番号 18 をハードコーディングしていることがわかりますが、これは拡張性が低く、次の currying でエレガントな関数式を使用してこの問題を解決する方法がわかります。

純粋な関数は、システムの複雑さを効果的に軽減するだけでなく、キャッシュ可能性などの多くの優れた機能も備えています:

import _ from 'lodash';
var sin = _.memorize(x => Math.sin(x));

//第一次计算的时候会稍慢一点
var a = sin(1);

//第二次有了缓存,速度极快
var b = sin(1);

3. 関数のカリー化

関数のカリー化 (カリー) の定義は非常に簡単です。関数にいくつかの引数を渡し、残りの引数を処理する関数を返すようにします。

たとえば、加算関数 var add = (x, y) => の場合は便利です。

上記の checkage 関数をまだ覚えていますか?次のようにカリー化できます:

//比较容易读懂的ES5写法
var add = function(x){
    return function(y){
        return x + y
    }
}

//ES6写法,也是比较正统的函数式写法
var add = x => (y => x + y);

//试试看
var add2 = add(2);
var add200 = add(200);

add2(2); // =>4
add200(50); // =>250

実際、カリー化は関数を「プリロード」する方法であり、渡すパラメーターを少なくすることで、これらのパラメーターをすでに記憶している新しい関数を取得します。つまり、これはある意味、パラメータの「キャッシュ」と関数を記述するための非常に効率的な方法:

var checkage = min => (age => age > min);
var checkage18 = checkage(18);
checkage18(20);
// =>true

4. 関数の組み合わせ

純粋な関数の使用方法とそれらをカリー化する方法を学んだ後、そのような「パッケージ」を書くのは非常に簡単になります。コード:

import { curry } from 'lodash';

//首先柯里化两个纯函数
var match = curry((reg, str) => str.match(reg));
var filter = curry((f, arr) => arr.filter(f));

//判断字符串里有没有空格
var haveSpace = match(/\s+/g);

haveSpace("ffffffff");
//=>null

haveSpace("a b");
//=>[" "]

filter(haveSpace, ["abcdefg", "Hello World"]);
//=>["Hello world"]

これも関数型コードではありますが、ある意味「エレガントではない」ことに変わりはありません。関数の入れ子の問題を解決するには、「関数合成」を使用する必要があります:

h(g(f(x)));

私たちが定義する合成は両面テープのようなもので、任意の 2 つの純粋な関数を組み合わせることができます。 3つの機能を兼ね備えた「三面テープ」はもちろん、「四面テープ」や「N面テープ」でも延長可能です。

この柔軟な組み合わせにより、ビルディングブロックのように関数コードを組み合わせることができます:

//两个函数的组合
var compose = function(f, g) {
    return function(x) {
        return f(g(x));
    };
};

//或者
var compose = (f, g) => (x => f(g(x)));

var add1 = x => x + 1;
var mul5 = x => x * 5;

compose(mul5, add1)(2);
// =>15

5. ポイントフリー

カリー化と関数の組み合わせの基本的な知識を踏まえて、以下のスタイルでポイントフリーコードを紹介しましょう。

注意していただければ、前のコードでは、オブジェクトに付属するメソッドの一部を常に純粋な関数に変換したいことに気づくかもしれません:

var first = arr => arr[0];
var reverse = arr => arr.reverse();

var last = compose(first, reverse);

last([1,2,3,4,5]);
// =>5


このアプローチには理由があります。

現時点では、Point Free には中国語の翻訳がありません。興味がある場合は、ここで英語の説明を読むことができます:

http://www.php.cn/

中国語の説明は、おそらく「ドン」です。次のような一時的な中間変数に名前を付けます。

var map = (f, arr) => arr.map(f);

var toUpperCase = word => word.toUpperCase();

この関数では、中間変数として str を使用しますが、この中間変数はコードが少し長くなる以外は意味がありません。このコードを変換しましょう:

//这不Piont free
var f = str => str.toUpperCase().split(' ');


このスタイルは、不必要な名前を減らし、コードをシンプルで多用途に保つのに役立ちます。もちろん、一部の関数でポイント フリー スタイルを記述するには、コードの他の部分はポイント フリーでない必要があり、ここで独自の選択を行う必要があります。

六、声明式与命令式代码

命令式代码的意思就是,我们通过编写一条又一条指令去让计算机执行一些动作,这其中一般都会涉及到很多繁杂的细节。

而声明式就要优雅很多了,我们通过写表达式的方式来声明我们想干什么,而不是通过一步一步的指示。

//命令式
var CEOs = [];
for(var i = 0; i < companies.length; i++){
    CEOs.push(companies[i].CEO)
}

//声明式
var CEOs = companies.map(c => c.CEO);


命令式的写法要先实例化一个数组,然后再对 companies 数组进行for循环遍历,手动命名、判断、增加计数器,就好像你开了一辆零件全部暴露在外的汽车一样,虽然很机械朋克风,但这并不是优雅的程序员应该做的。

声明式的写法是一个表达式,如何进行计数器迭代,返回的数组如何收集,这些细节都隐藏了起来。它指明的是做什么,而不是怎么做。除了更加清晰和简洁之外,map 函数还可以进一步独立优化,甚至用解释器内置的速度极快的 map 函数,这么一来我们主要的业务代码就无须改动了。

函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。

相反,不纯的不函数式的代码会产生副作用或者依赖外部系统环境,使用它们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于程序员的心智来说是极大的负担。

七、尾声

任何代码都是要有实际用处才有意义,对于JS来说也是如此。然而现实的编程世界显然不如范例中的函数式世界那么美好,实际应用中的JS是要接触到ajax、DOM操作,NodeJS环境中读写文件、网络操作这些对于外部环境强依赖,有明显副作用的“很脏”的工作。

这对于函数式编程来说也是很大的挑战,所以我们也需要更强大的技术去解决这些“脏问题”。我会在下一篇文章中介绍函数式编程的更加高阶一些的知识,例如Functor、Monad等等概念。

以上就是JavaScript函数式编程(一)的内容,更多相关内容请关注PHP中文网(www.php.cn)!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。