関数プログラミングの高次関数


3.高次関数
文章はJavaScript ES 6関数式プログラミング入門経典から整理します.
昨日はNodeの環境を配置しました.forEachという最初の関数式プログラミングAPIも作成しました.この関数は一つの関数を受信します.データ転送の代わりに関数を使用することができるという非常に強力な概念であり、この受信関数はそのパラメータの関数として高次関数(Higher-Order-Function)と呼ばれ、HOCと略称され、今日はいくつかの簡単なHOCを作成してクラスに追加します.始めましょう
3.1データの理解
各プログラミング言語にはデータタイプがあります.これらのデータタイプはデータを記憶してプログラムの機能を許可します.
JavaScriptは以下の種類のデータをサポートしています.
  • Number
  • String
  • Boolean
  • Object
  • Null
  • Udefined
  • Symbol(ES 6追加)
  • 重要なのは関数であり、JavaScriptのデータタイプとしても使用できます.関数はStringのようなデータタイプなので、それを伝えて他のデータタイプのペアの動作と非常に似ている変数に格納することができます.
    一つの言語許容関数が他のデータタイプとして使用される場合、関数は一等公民と呼ばれます.すなわち、関数は変数に値を与えられ、パラメータとして伝達され、他の関数によっても返されます.例を見ます
    //         
    var double = (value) => value * 2;
    
    //         
    var getJSON = (url,callback) =>{
        var xhr = new XMLHttpRequest();
        // ...    
        callback(xhr.resopnseText)
    }
    
    //          
    var delayAdd = (value) => {
        return function(value){
            return value + 1;
        }
    } 
    var add = delayAdd(1); //    add       
    var num = add(); //    add,     2
    
    3.2抽象と高次関数
    高次関数をどのように作成して実行するかを調べましたが、高次関数は何に使いますか?
    高次関数は一般的に抽象的で普遍的な問題に用いられる.言い換えれば、高次関数は抽象を定義することです.
    3.2.1抽象的な定義
    抽象とは何か?ここではウィキペディアの定義を引用します.
    ソフトウェアエンジニアリングとコンピュータ科学では、抽象的にコンピュータシステムの複雑さを管理する技術です.それは、システムと個人の相互作用の複雑さを確立することによって、より複雑な細部を現在のレベルに抑える.プログラマは理想的なインターフェースを使用しなければなりません.(通常は定義が良いです.)追加のレベルの機能を追加できます.そうでないと処理が複雑になります.
    紹介には次のような内容が含まれています.
    例えば、1つの作成が数値操作コードに関連するプログラマは、これらの詳細がどこで遮蔽されるかを含む、底面のハードウェアにおける数字の表現形式に興味を持たないかもしれない.これらは抽象的に出てきて、簡単な数字だけを残してプログラマに処理されたと言えます.
    簡単に言えば、抽象的なのは、私たちがあらかじめ定められた目標に集中して、下のシステム概念に関心を持たなくてもいいということです.
    3.2.2高次関数による抽象化
    昨日の私達のforEach関数を覚えていますか?
    const forEach = (array,fn){
        for(let i = 0,len = array.length; i < len; i++){
            fn(array[i]);
        }
    }
    
    上のforEach関数は抽象的に遍歴の問題を作り出して、ユーザーはどのように遍歴したのかを知る必要がなくて、このように問題は抽象的に出てきました.
    forEachは本質的に全体の行列を遍歴していますが、どのようにJavaScriptオブジェクトを遍歴しますか?ステップは以下の通りです
  • 所与のオブジェクトを巡回するkey
  • 、keyが自分のものかどうか識別する
  • .
  • であれば、keyの値
  • を取得する.
    上記のステップをforEachObjectという高次関数に抽象化して、次のようにします.
    // es6-function.js
    const forEachObject = (obj,fn) => {
        for(let key in obj){
            if(obj.hasOwnProperty(key)){
                //   key   value        fn
                fn(key,obj[key])
            }
        }
    }
    
    //   
    let object = {a:1, b:2};
    forEachObject(object,(k,v) => console.log(k + ':' + v))
    // a:1
    // b:2
    
    注意:forEachとforEachObjectは高次関数であり、開発者は任務に専念し、遍歴した部分を抽象的に作ってくれます.これらのエルゴード関数は抽象的に出てくるので、徹底的にそれらをテストすることができます.以下、抽象的に制御フローに対する処理を実現します.
    このために、unlessという関数を作成し、断言を受け付けます.falseと断言するとfnを呼び出します.コードは以下の通りです.
    // es6-function.js
    const unless = (predicate,fn) => {
        if(!predicate)
            fn()
    }
    
    この関数があれば、行列の偶数を検索するためのコードの一部を作成できます.
    forEach([1,2,3,4,5,6,7],(number) => {
        unless((number % 2),()=>{
            console.log(number + '    ')
        })
    })
    // 2    
    // 4    
    // 6    
    
    次に、もう一つのtimesという高次関数を見てみます.これは一つの数字を受け取り、调节者が提供する回数に応じて、着信関数を呼び出します.コードは以下の通りです.
    // es6-functional.js
    const times = (times,fn) => {
        for(let i = 0; i < times; i++)
            fn(i)
    }
    
    timesはforEach関数と似ていますが、違いはnumberで、arrayではなくnumberです.0-99の偶数を出力するなら、このように使えます.
    times(100,function(n){
        unless(n % 2,function(){
            console.log(n + "    ")
        })
    })
    
    上のコードを抽象的にサイクルし,条件判断を簡明な高次関数に置いた.
    3.3実際の高次関数
    前のセクションではいくつかの簡単な高次関数を書きました.このセクションでは実際の高次関数を知ることになり、簡単な高次関数から複雑な高次関数へと進みます.これらの関数はすべてJavaScript開発者が日常的に使うものです.
    3.3.1 every関数
    行列の内容が数字、配列または他のタイプかどうかを常にチェックする必要がありますが、一般的にはこの問題を解決するために典型的な循環法を編纂します.しかし、次のような抽象的なevery関数は、2つのパラメータを受信します.1つの配列と1つの関数は、行列のすべての要素がtrueであるかどうかを検査するために使用されます.つまり、行列のevery方法です.
    // es6-functional.js
    const every = (arr, fn) => {
        let result = true;
        for(let i = 0,len = arr.length; i < len; i++){
            result = result && fn(arr[i])
        }    
        return result;  
    }
    
    ここでは、単純に巡回して導入された配列を使用して、現在巡回している配列要素のコンテンツを呼び出します.なお、着信fnはブール値を返す必要がある.そして、私たちは&&演算を使って、すべての配列内容がfnの条件に従うことを確認します.
    NaN配列が入ってきました.fnとしてisNaNが入ってきました.与えられた数字がNaNであるかどうかを確認します.
    every([NaN,NaN,NaN], isNaN)
    // true
    every([NaN,NaN,4], isNaN)
    // false
    
    everry関数は典型的な高次関数であり,簡単で非常に有用である.続ける前に、私達はfor...ofサイクルを理解したいです.ES 6仕様の一部です.配列要素を巡回するために、for...ofサイクルでevery関数を書き換えます.
    // es6-functional.js
    const every = (arr, fn) => {
        let result = true;
        for(const value of arr){
            result = result && fn(value)
        }    
        return result;  
    }
    
    for...ofも古いfor(...)サイクルの抽象であり、インデックス変数を隠すことによって配列への遍歴が除去されます.私たちはeveryを使ってfor...ofを抽象化しました.次のバージョンのJavaScriptがfor...ofの使い方を変えたら、every関数で修正するだけで、これは抽象関数の最大の利点です.
    3.3.2 some関数
    everry関数と似ています.some関数も行列の一つの方法です.そして、関数の働き方はeveryとは反対です.行列の中の任意の要素が入ってきた関数を通じてtrueに戻りさえすれば、some関数はtrueに戻ります.some関数はany関数とも呼ばれる.SOme関数を実現するためには、&&& ではなく、𞓜124;を使用する必要があります.具体的なコードは以下の通りです.
    // es6-functional.js
    const some = (arr,fn) => {
        let result = false;
        for(const value of arr){
            result = result||fn(value)
        }
        return result;
    }
    
    注意:ここのsomeとevery関数はすべて効果がない実現です.everryは最初の不整合要素に出会う時に配列を巡回することを停止しなければなりません.そして、最初の整合要素に出会う時に配列を巡回することを停止するべきです.ここでは高次関数の概念を理解するためだけに、効率的なコードを作成するために
    some関数があれば、次の配列に入って検査します.
    some([NaN,NaN,4], isNaN)
    // true
    some([3,4,4], isNaN)
    // false
    
    someとeveryがどのように働いているかを知りました.次にsort関数と高次関数がどのようにその中で使われているかを見てみます.
    3.3.3 sort関数
    ソト関数はArayプロトタイプの内蔵関数です.果物のリストを並べ替える必要があると仮定します.var fruit = ['cherries','apples','bananas'];簡単にsort関数を呼び出すことができます.
    fruit.sort();
    // ['apples','bananas','cherries'];
    
    また、compare関数を受け入れることができます.提供されていない場合は、unicodeコード順に並べ替えられます.私たちは自分が提供したcompre関数を通じてJavaScriptのデータを並べ替えることができます.ソト関数の柔軟性の原因は高次関数の本質にあります.
    compre関数を作成する前に、実際に何を実現するべきかを確認します.
    function compare(a, b){
        if(/*          a < b */){
            return -1;
        }
        if(/*          a > b */){
        	return 1;    
        }
        return 0;
    }
    
    簡単な例を挙げると、リストがあると仮定します.
    var people = {
        {firstname: "aaFirstName", lastname: "cclastName"},
        {firstname: "ccFirstName", lastname: "aalastName"},
        {firstname: "bbFirstName", lastname: "bblastName"},   
    }
    
    現在は対象のfirstnameキーを使って人員を並べ替える必要があります.次のようにcompareに伝えられます.
    people.sort((a,b) => {
        return (a.firstname < b.firstname) ? -1:(a.firstname > b.firstname) ? 1:0
        /*
        	 
            if(a.firstname < b.firstname){
            	return -1;
            }else if(a.firstname > b.firstname){
                return 1
            }else{
                return 0
            }
        */
    })
    
    上のコードは下記のデータに戻ります.
    [
        { firstname: 'aaFirstName',lastName: 'cclastName'},
        { firstname: 'bbFirstName',lastName: 'bblastName'},
        { firstname: 'ccFirstName',lastName: 'aalastName'},    
    ]
    
    lastnameによるランキングは以下の通りです.
    people.sort((a,b) => {
        return (a.lastname < b.lastname) ? -1:(a.lastname > b.lastname) ? 1:0
        /*
        	 
            if(a.lastname < b.lastname){
            	return -1;
            }else if(a.lastname > b.lastname){
                return 1
            }else{
                return 0
            }
        */
    })
    
    戻ります
    [
        { firstname: 'ccFirstName',lastName: 'aalastName'},
        { firstname: 'bbFirstName',lastName: 'bblastName'},
        { firstname: 'aaFirstName',lastName: 'cclastName'},    
    ]
    
    compre関数の論理を見ています.
    function compare(a, b){
        if(/*          a < b */){
            return -1;
        }
        if(/*          a > b */){
        	return 1;    
        }
        return 0;
    }
    
    compre関数のアルゴリズムが分かりました.もっとよくできますか?上のロジックを一つの関数に抽象してもいいですか?上記の例から、私達はほぼ重複したコードで2つの関数を作成して、firstnameとlastnameを比較します.私たちが設計する関数は関数Wieパラメータではないが、関数を返します.
    以下で関数sortByを呼び出します.入力された属性に基づいて、ユーザーがオブジェクト配列を並べ替えることができます.コードは以下の通りです.
    // es6-functional.js
    const sortBy = (property) => {
        //       
        return (a,b) => {
            var result = (a[property] < b[property]) ? -1:(a[property] > b[property]) ? 1:0
            return result;
            /*
                 
                if(a[property] < b[property]){
                    return -1;
                }else if(a[property] > b[property]){
                    return 1
                }else{
                    return 0
                }
            */
        }
    }
    
    sortBy関数はpropertyというパラメータを受け取り、二つのパラメータを受け入れる新しい関数を返します.
    リターン関数は、compre関数の論理を明確に記述しています.
    属性名のfirstnameを使って関数を呼び出すと、関数は次のようにpropertyパラメータに置き換えられます.
    (a, b) => return (a['firstname'] < b['firstname']) ? -1:(a['firstname'] > b['firstname']) ? 1:0
    
    手動で関数を作成して所望の機能を実現した.sortByはこのように使えます.
    //    firstname   
    people.sort(sortBy('firstname'))
    
    //   
    [
        { firstname: 'aaFirstName',lastName: 'cclastName'},
        { firstname: 'bbFirstName',lastName: 'bblastName'},
        { firstname: 'ccFirstName',lastName: 'aalastName'},    
    ]
        
    //    lastname   
    people.sort(sortBy('lastname'))
    
    //   
    [
        { firstname: 'ccFirstName',lastName: 'aalastName'},
        { firstname: 'bbFirstName',lastName: 'bblastName'},
        { firstname: 'aaFirstName',lastName: 'cclastName'},    
    ]
    
    前と同じです
    本当に素晴らしいですsort関数は、sortBy関数で返されたcompre関数を受け取ります.多くのこのような高次関数があります.私達は再びcompre関数の背後にあるロジックを抽象化して、ユーザーが本当の需要に集中できるようにしました.結局、高次関数は抽象的です.
    3.4まとめ
    今日はJavaScriptがサポートするデータ構造から始まりました.関数もJavaScriptのデータ構造です.言い換えれば、関数は保存され、伝達され、値付けされ、この許容関数は別の関数に伝達されて高次関数と呼ばれる.高次関数は、パラメータとして他の関数を受け入れるか、関数に戻る関数です.
    この章では、開発者が困難な部分を抽象化するのに役立つ高次関数の概念を示すいくつかの例を見た.やはりみんなに手を取って書いたら、自分の理解が深まると思います.
    明日は高次関数での動作メカニズムを学びます.see you tomorrow!