JavaScript関数型プログラミング、本当の香の組み合わせ(2)

JavaScript関数型プログラミング、本物のシークレット関数型プログラミング(a )

この連載の記事はフロントエンドの初心者向けではありません。プログラミングの経験があること、およびJavaScriptの範囲、クロージャー、およびその他の側面の概念を理解する必要があります。

複合機能

組み合わせは、ソフトウェアの動作を明確にモデル化するための、シンプルで洗練された表現方法です。小さな、確定的な機能を組み合わせることによって、より大きなソフトウェアコンポーネントと機能を作成するプロセスは、編成、理解、デバッグ、拡張、テスト、および保守がより簡単なソフトウェアを生成します。

組み合わせについては、私はそれが関数型プログラミングの最も本質の1つであると思います、それで私は最初にこの概念を紹介するのを待つことができません。組み合わせてコードを書くことも、オブジェクト指向プログラミングや構造化プログラミングから考え方を変えるための重要なポイントです。

この組み合わせが継承よりも優れていることを証明したり、コードを組み合わせて記述したほうが良いとは思わないでください。この記事を読んで、組み合わせてコードを抽象化することをお勧めします。あなたの視野を広げて、あなたのコードをリファクタリングしたいとき、あるいは保守がより簡単なコードを書きたいときにアイデアを提供してください。

合成の概念は非常に直感的で、関数型プログラミングに固有のものではなく、私たちの生活のあらゆる場所やフロントエンドの開発において目に見えるものです。

たとえば、人気のあるSPA(シングルページアプリケーション)にはコンポーネントの概念がありますが、その理由は、いくつかの一般的な機能や要素の組み合わせを再利用可能にすることです。コンポーネントは、万能ではなくても、複雑なページを作成するときに単純なコンポーネントに分割してから、ニーズに合ったページにまとめることができます。

実際、関数型プログラミングの組み合わせは似ていますが、関数の組み合わせは簡単な全体プロセスで、分解を複雑な全体プロセスにまとめています。

文字列を入力し、その文字列を大文字に変換してから、順序を逆にします。

あなたはこのように書くかもしれません。

// 例 1.1

var str = 'function program'

// 一行代码搞定
function oneLine(str) {
    var res = str.toUpperCase().split('').reverse().join('')
    return res;
}

// 或者 按要求一步一步来,先转成大写,然后逆序
function multiLine(str) {
    var upperStr = str.toUpperCase()
    var res = upperStr.split('').reverse().join('')
    return res;
}

console.log(oneLine(str)) // MARGORP NOITCNUF
console.log(multiLine(str)) // MARGORP NOITCNUF

ここでは何も問題ないと思うかもしれませんが、今では製品は風変わりになり、要件を変更し、文字列を大文字にし、各文字を配列に組み立てました。 [A、A、A]になる。

それから、現時点で、以前にカプセル化した関数を変更する必要があります。これは、前のパッケージのコードを変更し、実際には、デザインモードで開閉の原則の破壊です。

それでは、このように初期要件コードを書くのであれば、それを関数型プログラミング方法で書いてください。

// 例 1.2

var str = 'function program'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

var toUpperAndReverse = 组合(stringReverse, stringToUpper)
var res = toUpperAndReverse(str)

そうすれば、ニーズが変わっても、以前にカプセル化したものを変更する必要はありません。

// 例 2

var str = 'function program'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

// var toUpperAndReverse = 组合(stringReverse, stringToUpper)
// var res = toUpperAndReverse(str)

function stringToArray(str) {
    return str.split('')
}

var toUpperAndArray = 组合(stringToArray, stringToUpper)
toUpperAndArray(str)

要件を変更するときに、前のパッケージのコードを壊すことなく、関数functionを追加してから、その関数を再アセンブルしたことがわかります。

ここでは、要件が変更された場合はコードを変更する必要があると言っている人がいるかもしれません。ここでは、開閉の原則とは、クラス、モジュール、関数などのソフトウェアエンティティが拡張機能に対して開放され、変更に対して閉鎖されるべきであることを意味すると宣言します。カプセル化され抽象化されたコードであり、呼び出されるロジックコードではありません。したがって、このように書くことは開閉の原則を破ることを意味するのではありません。

突然、製品は完全にフラッシュして、需要を変え、弦を大文字にし、それからひっくり返し、そしてそれを配列に変換したかったのです。

あなたが前の考えに従い、抽象化しないなら、あなたは精神的に1万頭の牧草馬でなければなりません、しかし抽象化するなら、あなたは慌てることはできません。

// 例 3

var str = 'function program'

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

function stringToArray(str) {
    return str.split('')
}

var strUpperAndReverseAndArray = 组合(stringToArray, stringReverse, stringToUpper)
strUpperAndReverseAndArray(str)

以前パッケージ化したコードを置き換えるのではなく、単に関数の組み合わせを変更しただけです。ご覧のとおり、組み合わせる方法は、実際には単一の関数の機能を抽象化してから複雑な機能を形成することです。このアプローチはあなたの抽象的な能力を行使するだけでなく、メンテナンスにも大きな利便性をもたらします。

しかし、上記の組み合わせを漢字に置き換えたばかりなので、この組み合わせをどのように実装すればよいでしょうか。まず、これが関数であり、パラメータも関数であり、戻り値も関数であることがわかります。

例2、2つの関数を組み合わせる方法、上記に従って、パラメータと戻り値は関数であり、関数の基本構造は次のように決定できます(ところで、組み合わせは英語のcomposeに置き換えられます)。

function twoFuntionCompose(fn1, fn2) {
    return function() {
        // code
    }
}

もう一度考えてみましょう。関数composeを使用しない場合、例2で2つの関数を合成する方法は、組み合わせの目的を達成するためにこれを実行することもできます。

var res = stringReverse(stringToUpper(str))

それから、このロジックに従って、 twoFuntonCompose の実装を書くことができます。

function twoFuntonCompose(fn1, fn2) {
    return function(arg) {
        return fn1(fn2(arg))
    }
}

同様に、3つの関数の組み合わせ関数、4つの関数の組み合わせ関数を書くこともできます。これは、単に複数のレイヤーを入れ子にしただけのものです。

function multiFuntionCompose(fn1, fn2, .., fnn) {
    return function(arg) {
        return fnn(...(fn1(fn2(arg))))
    }
}

この嫌なやり方は明らかに私達のプログラマーがすべきことではない、そしてそれから私達はいくつかの規則を見ることができる、最後の関数に直接行くとき、次の戻り値のパラメータとして前の関数の戻り値ただ戻る。

だから、通常の考え方に従うと、このように書かれます。

function aCompose(...args) {
    let length = args.length
    let count = length - 1
    let result
    return function f1 (...arg1) {
        result = args[count].apply(this, arg1)
        if (count <= 0) {
          count = length - 1
          return result
        }
        count--
        return f1.call(null, result)
    }
}

これは書くことは問題ありません、アンダースコアもこのように書かれています、しかしまだ頑健な取り扱いがたくさんあります、コアはおそらくこのようです。

しかし、機能的な熱狂者として、機能的な方法で考えるようにしてください、それで次のコードを書くためにreduceRightを使ってください。

function compose(...args) {
    return (result) => {
        return args.reduceRight((result, fn) => {
          return fn(result)
        }, result)
  }
}

このコンポーズを実装するための5つのアイデアこの記事を読む前は、他の3つについては考えていませんでしたが、あまり役に立ちませんでしたが、考えを広げることができます。興味のある学生は見てみることができます。

注:compose関数に渡す指定は、最初に最後のパラメーターから実行され、最初のパラメーターまで実行されますまた、パラメーターとしてポーズに渡される関数にも必要です。そして、関数の戻り値は次の関数の引数です。

最後の関数から評価するために作成するために、あまり快適でない場合は、pipe関数を使用して左から右に実行できます。

function pipe(...args) {
     return (result) => {
        return args.reduce((result, fn) => {
          return fn(result)
        }, result)
  }
}

実装はcomposeと似ていますが、パラメータのトラバースが右から左( reduceRight )から左から右( reduce )に変更されている点が異なります。

これまでにたくさんの記事を読んだことがありますか?作成やカレー化、アプリケーションやその他の機能を実装する方法を知っているかもしれませんが、コニャックに使用され、使用されていないことを知らないかもしれません。この記事を読んだ後、あなたがこれを簡単に達成できることを願っています。 Curryといくつかのアプリケーションの実装は後で議論され続けるでしょう。

ポイントフリー

関数型プログラミングの世界では、そのような非常に人気のあるプログラミングスタイルがあります。このスタイルは暗黙のプログラミングとも呼ばれ、ポイントフリーとも呼ばれ、pointは仮パラメータを表します。つまり、仮パラメータを使用するプログラミングスタイルはありません。

// 这就是有参的,因为 word 这个形参
var snakeCase = word => word.toLowerCase().replace(/\s+/ig, '_');

// 这是 pointfree,没有任何形参
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

パラメータ化された関数の目的は単一のデータを取得することであり、ポイントフリー関数の目的は別の関数を取得することです。

それでは、このポイントフリーの使用は何ですか?それは私たちが関数に集中することを可能にします、パラメータ命名の問題は間違いなく保存されています、そしてコードはより簡潔でエレガントです。ポイントフリー関数は、多くの非ポイントフリー関数から構成されることがあり、すなわち、基礎となる基本関数はほとんどパラメータ化されており、ポイントフリーは基本関数と組み合わされた拡張関数で具現化されることに留意されたい。さまざまな基本機能を組み合わせることによって、複製されたビジネスロジックを形成するためのビジネス機能として使用されることがよくあります。

ポイントフリーは私たちのプログラミングをより美しくそして宣言的に見せるものと言えますが、これは関数型プログラミングにおける一種の追求であり、可能な限りポイントフリーを書くことができますが、過度に使わないでください。過度の使用は正しくありません。

さらに、composeによって結合された基本関数には1つのパラメーターしかないことがわかりますが、基本関数のパラメーターは複数あることが多いので、今回はマジック関数(Curry関数)を使用します。

カレー

ウィキペディアでは、カリー化として定義されています。

コンピュータサイエンスでは、カリー化(英語:カリー化)は、キャリングまたは Califorating として翻訳されていますが、 a href = “https://zh.wikipedia.org/w/index.php%3Ftitle%3D%25E5%25BD%25A2%25E5%25BC%258F%25E5%258F%2582%25E6%2595%25B0%26action% パラメータ %2595%25B0 “rel =” nofollow noopener noreferrer “target =” _ blank “>関数は、単一のパラメータ(元の関数の最初のパラメータ)を受け取る関数に変換されて返されます。残りのパラメータを受け入れて新しい関数の結果を返すのテクニック。

定義には、さらに2つの重要な情報があります。

これら2点は関数構成パラメーターの要件ではなく、複数のパラメーターの関数を単一のパラメーターを受け入れる関数に変換することができますが、複数のパラメーターを使用すると上記の基本関数を使用できないという問題を解決できますか?したがって、これはカレー関数の役割について非常に明白です。

カレー関数を使用すると、ポイントフリーをさらに追求し、コードをより美しくすることができます。

次に、カレーを理解するための例を見てみましょう。

例えば、あなたがShiduoストアを持っていて、あなたの割引顧客に10%の割引を与えたい(すなわち10%の割引):

function discount(price, discount) {
    return price * discount
}

値引きされた顧客が500ドル相当の商品を購入するとき、あなたは彼に割引を与えます:

const price = discount(500, 0.10); // $50 

ご想像のとおり、長期的には、毎日10%の割引を計算しています。

const price = discount(1500,0.10); // $150
const price = discount(2000,0.10); // $200
// ... 等等很多

毎回この0.10の割引を常に増やす必要がないように、割引関数を明確にすることができます。

// 这个就是一个柯里化函数,将本来两个参数的 discount ,转化为每次接收单个参数完成求职
function discountCurry(discount) {
    return (price) => {
        return price * discount;
    }
}
const tenPercentDiscount = discountCurry(0.1);

今、私たちはあなたの顧客が買うアイテムの価格だけを計算することができます:

tenPercentDiscount(500); // $50

同様に、いくつかの割引顧客はいくつかの割引顧客よりも重要です – それらをスーパー顧客と呼びましょう。そして私達はこれらのスーパーカスタマーに20%の割引をしたいと思います。
あなたは私たちのカレーの割引機能を使うことができます:

const twentyPercentDiscount = discountCurry(0.2);

カレー割引関数の割引を0.2(つまり20%)に調整することで、スーパーカスタマーに新しい機能を追加しました。
返された関数twentyPercentDiscountは、スーパーカスタマーの割引を計算するために使用されます。

twentyPercentDiscount(500); // 100

上記の** discountCurry **を通して、あなたはカレーについて少し感じていると思いますが、この記事は関数型プログラミングにおけるカレーの応用についてですので、それを関数に適用する方法を見てみましょう。

与えられた文字列を最初に反転してから大文字にして、 TAOWENG があるかどうかを調べます。そうでなければnoを出力します。

function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

function find(str, targetStr) {
    return str.includes(targetStr)
}

function judge(is) {
    console.log(is ? 'yes' : 'no')
}

これら4つの関数は簡単に書くことができます。最初の2つはすでに上で記述されています。そしてfind関数も非常に単純です。今度はcomposeを使ってポイントフリーを実現したいのですがパラメータは、composeパラメータの要件を満たしていません。今回は前の例のように、検索機能を明確にしてから組み合わせます。

// 柯里化 find 函数
function findCurry(targetStr) {
    return str => str.includes(targetStr)
}

const findTaoweng = findCurry('TAOWENG')

const result = compose(judge, findTaoweng, stringReverse, stringToUpper)

ここで見ているのは、カレー化が少ないパラメータで段階的にポイントフリーを達成するのに非常に役立つということではありません。

しかし、上記の方法では、Curryingは以前にパッケージ化されていた関数を変更する必要があり、これも開閉の原則を破壊し、いくつかの基本的な関数ではソースコードを変更します。問題があるので、手動でcurrizeする関数を書くべきです。

定義の前のカレーの定義と最初の2つのカレー関数に基づいて、バイナリ(パラメーター2)一般化カレー関数を書くことができます。

function twoCurry(fn) {
    return function(firstArg) { // 第一次调用获得第一个参数
        return function(secondArg) { // 第二次调用获得第二个参数
            return fn(firstArg, secondArg) // 将两个参数应用到函数 fn 上
        }
    }
}

したがって、上記のfindCurryはtwoCurryによって取得できます。

const findCurry = twoCurry(find)

このようにして、パッケージ化された関数を変更し、カレーを使い、そして関数を結合することができます。しかし、ここでは二項関数のカリー化のみを実装しています、三項の場合、四項は三項カリー関数、四項カリー関数を書く必要があるかどうかです。元Ke Lihua。

function currying(fn, ...args) {
    if (args.length >= fn.length) {
        return fn(...args)
    }
    return function (...args2) {
        return currying(fn, ...args, ...args2)
    }
}

ここでは再帰的な考え方を用いていますが、得られたパラメータ数がfnのパラメータ数以上であれば、パラメータが得られたことが証明されるので直接fnを実行し、得られなければ再帰的に求めます。

一般的なCurrying関数の基本的な考え方は非常に単純で、コードは非常に簡潔で、1回の呼び出しで複数のパラメータを渡す機能もサポートしています(ただし、これは複数のパラメータとカレーの定義を渡します)。あまり良くないので、カレーの変種として使うことができます。)

ここでの私の焦点は、Curryの実装には当てはまらないので、あまり堅牢なものを書かなかったので、より強力なCurry関数を見ることができます。 JavaScriptの機能関数カレー

部分適用

アプリケーションの一部は、変数の不変パラメータのサブセットを固定値に初期化することによって、より小さな基本関数を作成する操作です。簡単に言えば、5つのパラメータを持つ関数がある場合、3つのパラメータを与えた後、あなたは1つまたは2つのパラメータを取得します。

上記の定義を見ると、これは関数パラメータの長さを短くするために使用されるCurryと非常によく似ていると思われるかもしれません。

function debug(type, firstArg, secondArg) {
    if(type === 'log') {
        console.log(firstArg, secondArg)
    } else if(type === 'info') {
        console.info(firstArg, secondArg)
    } else if(type === 'warn') {
        console.warn(firstArg, secondArg)
    } else {
        console.error(firstArg, secondArg)
    }
}

const logDebug = 部分应用(debug, 'log')
const infoDebug = 部分应用(debug, 'info')
const warnDebug = 部分应用(debug, 'warn')
const errDebug = 部分应用(debug, 'error')

logDebug('log:', '测试部分应用')
infoDebug('info:', '测试部分应用')
warnDebug('warn:', '测试部分应用')
errDebug('error:', '测试部分应用')

debug メソッドは、コンソールオブジェクトを使ってデバッグするために通常使用するさまざまなメソッドをカプセル化したもので、3つのパラメータを渡すことになっています。異なるメソッドを呼び出す必要があり、必要なパラメータを渡します。

私の場合は、これをカプセル化する必要はなく、作業量も減少しませんが、デバッグ時にコンソールに出力するだけでなく、デバッグ情報をデータベースに保存するなどの場合は、このパッケージは役に立ちましたか?

一部のアプリケーションではパラメータを減らすこともできるので、組み合わせ関数を作成するときにも場所があり、必要なパラメータを速く渡すことができます。パラメータはcomposeに渡したままにします。定義によれば、関数呼び出しは1つのパラメーターしか渡すことができません4つまたは5つのパラメーターがある場合は、次のことを行う必要があります。

function add(a, b, c, d) {
    return a + b + c +d
}

// 使用柯里化方式来使 add 转化为一个一元函数
let addPreThreeCurry = currying(add)(1)(2)(3)
addPreThree(4) // 10

この種の継続的な呼び出し(ここでのcurracyは、我々が書いたカレー化の変形ではなく、カレー化の定義に基づいています)が、いくつかのアプリケーションでは:

// 使用部分应用的方式使 add 转化为一个一元函数
const addPreThreePartial = 部分应用(add, 1, 2, 3)
addPreThree(4) // 10

このアプリケーションのいくつかの機能の役割を理解したので、これを実装するのはとても簡単です。

// 通用的部分应用函数的核心实现
function partial(fn, ...args) {
    return (..._arg) => {
        return fn(...args, ..._arg);
    }
}

さらに、アプリケーションのこの部分がJavaScriptのbind関数と非常によく似ていることがわかったかどうかわかりませんが、最初に入れられたパラメーターはクロージャー存在関数を介して渡され、それから他のパラメーターは再度呼び出されると関数に渡されます。ただし、アプリケーションによってはこれを指定する必要がないため、bindを使用して部分アプリケーション機能を実装することもできます。

// 通用的部分应用函数的核心实现
function partial(fn, ...args) {
    return fn.bind(null, ...args)
}

さらに、実際には、カレーと一部のアプリケーションは本当に似ているため、これら2つのテクノロジは混同しやすいことがわかります。両者の主な違いは、内部メカニズムとパラメータ受け渡しの制御です。

まとめ

この記事では、私たちの要求を組み合わせて完成させるための関数を紹介し、関数型プログラミングスタイルを紹介します。ポイントフリー形式(可能な限り、必ずしもそうとは限りません)は、構成パラメータまたはパイプパラメータのパラメータと一致して、関数パラメータを減らすためにカリー化または部分適用の使用を導入します。

そのため、この記事の焦点は、機能を組み合わせる方法、および複雑な機能をより小さな単一機能の機能に抽象化する方法を理解することです。これにより、私たちのコードはより保守しやすくなり、宣言的になります。

この記事で言及されている他の概念、つまりクロージャ、スコープ、その他のカレーの使い方については、補足でもっと深く理解していただきたいと思います。この記事は主に関数の組み合わせの習得についてです。

参考記事

この記事は、独自の個人ウェブサイト Taoyuan に掲載されました githubブログにもあります。

あなたが興味を持っているなら、あなたはまた私の個人公衆番号に従うことができます: “Front Taoyuan”

元のリンク