ホームページ  >  記事  >  ウェブフロントエンド  >  関数型プログラミングの概要と概要 (コード付き)

関数型プログラミングの概要と概要 (コード付き)

yulia
yuliaオリジナル
2018-09-11 17:01:311750ブラウズ

最近、関数型プログラミングの入門書を読んでいて、自分なりにまとめてみましたので、興味がある方、必要な方はご覧ください。

1. 関数型プログラミングとは

関数型プログラミングは主に数学の関数とその考え方に基づいています。まず数学の関数、つまり
y = f(x)
、つまり関数 f について復習しましょう。 (x ) は x をパラメーターとして、y を結果として受け取ります。これにはいくつかの重要な点が含まれます:
1. 関数は常に値を返す必要があります
3.関数は、外部環境ではなく、受信したパラメーター (x など) に基づいて実行される必要があります。 4. 指定された x に対して、一意の y のみが出力されます。この関数を分析するために、まず次の例を見てみましょう。 3 番目の項目は、関数は外部環境ではなく、受け取ったパラメーターに従って実行する必要があります。ここでの関数 CalculateTax は外部のpercentValueに依存しているため、この関数を数学的な意味での関数と呼ぶことはできません。では、どうすればこの関数を数学的な意味での関数に変換できるでしょうか。非常に単純です

var CalculateTax = (value,percentValue) => {return value/100 * (100 +percentValue)};

これで、この関数は実際の関数と呼ぶことができます。

それでは、関数型プログラミングを簡単な技術用語で定義してみましょう。関数型プログラミングは、関数が入力と出力のみに依存して独自のロジックを完成させるパラダイムです。これにより、関数が複数回呼び出された場合に同じ結果が返されるようになります。この関数は外部環境変数を変更しません。

2. 参照の透明性


関数の定義によれば、すべての関数は同じ入力と出力に対して同じ値を返すと結論付けることができます。関数のこの特性は参照透明性と呼ばれます

例として

// 一个计税函数
var percentValue = 5;
var calculateTax = (value) => {return value/100 * (100 + percentValue)};
関数内のグローバル変数に依存せず、単に入力を返す単純な関数を定義します。ここで、それが

var identity = (i) => { return i };

のような他の関数呼び出しの間に適用されると仮定します。 参照透過性の定義に従って、それを

sum(4,5) + identity(1);

に変換できます。 このプロセスは置換モデルと呼ばれ、関数の結果を直接置き換えることができます (主に、この関数のロジックは他のグローバル変数に依存しません)。

関数は指定された入力結果に対して同じ値を返すため、実際にそれをキャッシュすることができます。たとえば、階乗を計算する関数「factorial」があります。階乗を計算するために引数を受け取ります。たとえば、5 の階乗は 120 です。ユーザーが 2 回目に 5 の階乗を入力すると、参照の透明性により (同じ入力に対して同じ結果が返されます)、結果が 120 であることがわかりますが、マシンにはそれを認識させる必要があります。マシンが結果をキャッシュすることで、今後の呼び出しで結果を再度計算することなく直接返すことができます。これは、関数型プログラミングにおける参照の透過性とキャッシュ可能なコードの重要性を示しています。

3. 関数型、宣言型、抽象型


関数型プログラミングは、宣言型プログラミングと抽象コードの記述を推奨します

宣言型プログラミングとは何ですか? 配列内のすべての要素を出力したいとすると、次のメソッド

sum(4,5) + 1;
を使用できます。このコードでは、コードに何を実行するかを正確に指示します。例: 配列の長さの取得、配列のループ、インデックスによる各要素の取得。これは命令型プログラミングです。命令型プログラミングは、コンパイラーに何をすべきかを指示することを支持します。

別の方法を見てみましょう

var array = [1,2,3];
for(let i = 0; i < array.length; i++){
    console.log(array[i])
}

上記のコードでは、配列の長さの取得、配列のループ、インデックスを使用した配列要素の取得などを削除しました。私たちが気にするのは、何をするか (配列要素の出力など)、配列の長さの取得、ループなどはすべてマシンによって行われます。私たちが気にする必要があるのは、何を行うかだけであり、方法ではありません。宣言型プログラミング
関数型プログラミングの提案 コード内の他の場所で再利用できる抽象的な方法で関数を作成します。


4. 純粋関数


純粋関数とは何ですか?純粋関数関数は、指定された入力に対して同じ出力を返します。たとえば、

var arr = [1,2,3];
arr.forEach((ele) => { console.log(ele) })

上の double は、同じ入力に対して常に同じ出力を返すため、純粋関数です。純粋な関数は参照透過性に従うため、単純に double(5) を 10 に置き換えることができます。では、純粋関数の何がすごいのでしょうか?見てみましょう1.4.1 純粋関数はテスト可能なコードを生成します

不純関数には副作用があります。例として前の税金計算関数を見てみましょう

var double = (value) => value * 2;

この関数は主に外部環境に依存するため、純粋関数ではありません。そのロジックを計算し、外部環境が変化すると結果に影響を与えます。したがって、純粋関数の主な特徴は、外部変数に依存せず、外部変数を変更すべきではないことです。外部変数を変更すると、他の関数の動作が変更される可能性があります。つまり、副作用が発生し、システムの動作が予測不能になる可能性があります。

1.4.2 合理的なコード

double function など、名前から関数の機能を推測する必要があります

var double = (value) => value * 2;

我们可以通过函数名轻易的推出这个函数会把给定的数值加倍,因此根据引用透明性,我们可以直接把 double(5) 替换成 10。还有一个例子,Math.max(3,4,5,6) 结果是什么?虽然我们只看到了函数的名字,但是我们很容易看出结果,我们看到实现了吗?并没有,为什么,就是因为 Math.max 是纯函数啊!!!

5、 并发代码

纯函数允许我们并发的执行代码,因为纯函数不会改变它的环境,这意味着我们根本不需要担心同步问题。当然,js 是单线程的,但是如果项目中使用了 webworker 来并发执行任务,该怎么办?或者有一段 Node 环境中的服务端代码需要并发的执行函数,又该怎么办呢?

// 非纯函数代码
let global = &#39;something&#39;
let function1 = (input) => {
    // 处理 input
    // 改变 global
    global = "somethingElse"
}
let function2 = () => {
    if(global === "something"){
        // 业务逻辑
    }
}

如果我们需要并发的执行 function1 和 function2,假设 function1 在 function2 之前执行,就会改变 function2 的执行结果,所以并发执行这些代码就会造成不良的影响,现在把这些函数改为纯函数。

let function1 = (input,global) => {
    // 处理 input
    // 改变 global
    global = "somethingElse"
}
let function2 = (global) => {
   if(global === "something"){
        // 业务逻辑
    }
}

此处我们把 global 作为两个函数的参数,让它们变成纯函数,这样并发执行的时候就不会有问题了。

6、可缓存

既然纯函数对于给定的输入总能返回相同的输出,那么我们就能缓存函数的输出,例如

var doubleCache = (value) => {
    const cache = {};
    return function(value){
        if(!cache[value]){
            cache[value] = value * 2
            console.log(&#39;first time&#39;)
        }
        return cache[value];
    }
}
var double = doubleCache();
double(2) // first time,4
double(2) // 4
// 或者直接使用立即执行函数
var double = ((value) => {
    const cache = {};
    return function(value){
        if(!cache[value]){
            cache[value] = value * 2
            console.log(&#39;first time&#39;)
        }
        return cache[value];
    }
})()
double(2) // first time,4
double(2) // 4

这个函数中,假设我们第一次输入 5,cache 中并没有,于是执行代码,由于闭包的存在,cache[5] = 10,第二次我们调用的时候,cache[5] 存在,所以直接 return 10,看到了吗?这就是纯函数的魅力!!!别忘记这是因为纯函数的引用透明性。

7、 管道与组合

纯函数应该被设计为一次只做一件事,并且根据函数名就知道它所做的事情。
比如 linux 系统下有很多日常任务的命令,如 cat 用于打印文件内容,grep 用于搜索文件,wc 用于计算行数,这些命令一次只解决一个问题,但是我们可以用管道或组合来完成复杂的任务。假设我们需要在一个文件中找到一个特定的名称并统计它的出现次数,在命令行要输入如下指令
cat jsBook | grep -i “composing” | wc
上面的命令通过组合多个函数解决了我们的问题。组合不是 linux 命令独有的,它们是函数式编程范式的核心。
我们把它们称为函数式组合。来看一个 compose 函数的例子

var add1 = (value) =>{ return value+1 };
var double = (value) => {return value*2 };
var compose = (a,b) => {
    return (c) => {
       return a(b(c));
    }
}
var doubleAndAdd1 = compose(add1,double);
doubleAndAdd1(5) // 打印 5 * 2 + 1 = 11

compose 函数返回一个函数,将 b 的结果作为 a 的参数,这里就是将 double 的结果作为 add1 的参数,来实现了函数的组合。

8、 纯函数是数学函数

还记得我们之前的缓存函数吗,假设我们多次调用 double 对象,那么 cache 中就会变成这样

{
    1: 2,
    2: 4,
    3: 6,
    4: 8,
    5: 10
}

假设我们设置 double 的输入范围限制为 1 - 5,而且我们已经为这个范围建立的 cache 对象,因此只要参照 cache 就能根据指定输入返回指定输出。也就是一一对应的关系。
那么数学函数的定义是什么呢?
在数学中,函数是一种输入集合和可允许的输出集合之间的关系,具有如下属性:每个输入都精确地关联一个输出。函数的输入称为参数,输出称为值。对于一个给定的函数,所有被允许的输入集合称为该函数的定义域,而被允许的输出集合称为值域。

上面的定义和纯函数完全一致,例如在 double 中,你能找到定义域和值域吗?当然可以!通过这个例子,可以很容易看到数学函数的思想被借鉴到函数式范式的世界

9、 我们要做什么?

我们将通过学习,构建出一个 ES6-Functional 的函数式库,通过构建的过程,我们将理解如何使用 JavaScript 函数,以及如何在日常工作中应用函数式编程。

10、小结

这一节我们只是简单的介绍了函数式编程的概念,以及什么是纯函数,其中最重要的就是引用透明性。然后研究了几个短小的例子,通过例子来加深对函数式编程的理解。接下来我们将一步一步深入了解函数式编程。

以上が関数型プログラミングの概要と概要 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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