『JavaScriptモード』読書ノート(4)—関数5

14558 ワード

これは関数部分の最後の編です.Curry化について話しましょう.
十、Curry
この部分は主にCurry化と部分関数応用の内容について論じた.しかし、深く議論する前に、まず関数応用の意味を知る必要があります.
 
関数の適用
いくつかの純粋な関数式プログラミング言語では、関数は呼び出し(すなわちcaledまたはinvoked)として記述されておらず、アプリケーションとして記述されている.JavaScriptでは、同じことができます.関数は、メソッドFunction.prototype.appy()を使用して適用されます.これはJavaScriptの関数が実際にオブジェクトであり、また、次のような方法があります.
//     
var sayHi = function(who) {
    console.log("Hello" + (who? ", " + who : "") + "!");
};

//     
sayHi(); //   "Hello"
sayHi('world'); //   "Hello, world!"

//     
sayHi.apply(null, ["hello"]); //   "Hello, hello!"
上記の例で見たように、関数の呼び出しとアプリケーションの関数は完全に同じ結果を得ることができます.apply()は2つのパラメータを有しています.最初のパラメータは関数の内部にあるthisに結びつけるオブジェクトであり、2番目のパラメータは1つの配列または複数のパラメータ変数であり、これらのパラメータは関数の内部に使用できる類似の配列のargmentsオブジェクトになります.最初のパラメータがnull(空)の場合、thisはグローバルオブジェクトに向けられます.このとき得られた結果は、指定されていないオブジェクトを呼び出す時の方法と同じです.
関数がオブジェクトの方法である場合、null参照は伝えられません.この場合、ここのオブジェクトは、appy()の最初のパラメータとなります.
//     
var alien= {
    sayHi: function(who) {
        console.log("Hello" + (who? ", " + who : "") + "!");
    }
} 
alien.sayHi('world'); //   "Hello, world!"
sayHi.apply(alien, ["humans"]); //   "Hello, humans!"
上のコードでは、sayHi()内部のthisがalienオブジェクトを指しています.これまでの例では、thisはグローバルオブジェクトを指していた.
上記の2つの例で示したように、これらは、私たちが考えている「関数を呼び出す」ということは、単に「シンタックス」ではなく、関数適用に等しいことを示しています.
注意してください.appy以外にも、Function.prototypeオブジェクトにはもう一つのcall方法がありますが、これはまだappy()の上にあるシンタックスキャンディーだけです.このシンタックスキャンディーを使用することが望ましい場合があります.すなわち、関数がパラメータを一つしか持っていない場合は、実際の状況に応じて、要素だけの配列を作成することは避けられます.
//       ,       ,       
sayHi.apply(alien,["humans"]);
sayHi.call(alien,"humans");
 
部分的に適用
コール関数は実際に一つのパラメータセットを一つの関数に適用するということを知っていますが、すべてのパラメータではなく部分パラメータだけを伝えることができますか?この場合は数学関数を手動で処理するためによく用いられる方法と似ている.関数add()があると仮定して、2つの数字を一緒に加算します.xとy.以下のコードセグメントは、所与のx値が5であり、y値が4である場合の解決策を示している.
//        
//       JavaScript
function add(x,y) {
    return x + y;
}
//      
add(5,4);

//  1 ,      
function add(5, y){
    return 5 + y;
}

//  2 ,      
function add(5, 4) {
    return 5 + 4;
}
もう一度注意してください.第1、2ステップのコードは不正です.目的だけを示します.
上のコードセグメントは,部分関数応用の問題を手動で解決する方法を実証した.最初のパラメータの値を取得し、未知のxを関数全体で既知の値5で置換し、全てのパラメータがなくなるまで同じステップを繰り返すことができる.
この例におけるステップ1は、部分的なアプリケーション(partial appration)と呼ばれることができ、すなわち、我々の金鷹は最初のパラメータを使用した.一部のアプリケーションを実行すると、結果は得られず、逆に別の関数が得られます.
以下のコードセグメントは、故郷のpartialAppley()方法の使用例を示しています.
var add = function (x,y) {
    return x + y;
};

//     
add.apply(null,[5,4]); // 9

//     
var newadd = add.partialApply(null,[5]);
//            
newadd.apply(null,[4]); // 9
上のコードに示されているように、一部のアプリケーションは他の新しい関数を提供し、その後、他のパラメータで関数を呼び出します.このような動作は、実際にはadd(5)(4)と類似しています.これは、add(5)が後に呼び出される関数を返すためです.
また、私たちが熟知しているadd(5,4)の呼び方は「シンタックス」ではないかもしれません.反対に、add(5)(4)を使って「シンタックス」のようです.
現在、現実に戻っても、JavaScriptにはpartialAppley()の方法と関数がありません.デフォルトでは上記と同様の行為はありません.しかし、JavaScriptのダイナミック性はこのような挙動をサポートするのに十分であるので、これらの関数を構築することができる.
関数を理解して処理する過程はCurryプロセスになる.
 
Curry化
ここのcurryは数学者Haskell Curryの名前から由来しています.Curry化は関数変換を行う過程である.では、どのように関数をCurry化しますか?他の関数言語は、このCurry変換を言語自体に構築し、すべての関数がデフォルトで変換されています.JavaScriptでは、一部のアプリケーションを処理するためのCurry化関数にadd()関数を変更することができます.
以下、例を見てみましょう.
// curry  add()  
//         
function add(x,y) {
    var oldx = x,oldy = y;
    if(typeof oldy === 'undefined') { //   
        return function(newy) {
            return oldx + newy;
        };
    }
    //     
    return x + y;
}

//   
console.log(typeof add(5)); //   “function”
add(3)(4); // 7
//           
var add2000 = add(2000);
add2000(19); //  2010
上のコードセグメントでは、最初にadd()を呼び出すと、戻る内部関数のためのクローズドが作成されます.この閉じたパケットは、元のx値とy値をプライベート変数oldxとoldyに格納する.最初のプライベート変数oldxは内部関数実行時に使用されます.部分的に適用されず、xとy値を同時に伝達すると、この関数は継続して実行され、簡単に加算される.このadd()の実現は実際の需要に比べてかなり長いように見えるが、ここではデモの目的だけで実現される.以下はより簡素化された実装バージョンを表示します.ここでは、oldxとoldyは存在しない.元のx陰式がクローズドに格納されているだけでなく、yを局所変数として多重化し、前のように新たな変数newyを作成する.
// curry  add()  
//         
function add(x, y) {
    if(typeof y === 'undefined') { //  
        return function(y) {
            return x + y;
        };
    }
    //     
    return x + y;
}
これらの例では、関数add()自体が処理部分アプリケーションを担当している.しかし、同じタスクをより一般的に実行できますか?つまり、任意の関数を新しい部分パラメータの関数に変換できますか?
function schonfinkelize(fn) {
    var slice = Array.prototype.slice,
        stored_args = slice.call(arguments,1);
    return function () {
        var new_args = slice.call(arguments),
            args = stored_args.concat(new_args);
        return fn.apply(null,args);
    }
}
Shonfinkelize()関数はこんなに複雑ではないはずですが、JavaScriptの中でargmentsは本物の配列ではないからです.Aray.prototypeからslice()方法を借りると、argmentsを行列に変えて、この配列を使うのがもっと便利です.schone finkelize()が初めて呼び出されたとき、slice(slice)メソッドを指す私有参照が格納され、この方法を呼び出したパラメータ(stordualrgsに預け入れ)も格納されています.この方法は最初のパラメータだけが剥離されています.これは最初のパラメータがcurry化される関数です.そして、新しい関数を返しました.この新しい関数が呼び出されたときには、すでにプライベートに記憶されているパラメータstored uuにアクセスしました.args及びsliceは引用します.この新しい関数は、既存の部分アプリケーションパラメータを新しいパラメータ(newuargs)に統合して、元の関数fnに適用しなければなりません.
 
 
上記の変換方法をテストします.
function schonfinkelize(fn) {
    var slice = Array.prototype.slice,
        stored_args = slice.call(arguments,1);
    return function () {
        var new_args = slice.call(arguments),
            args = stored_args.concat(new_args);
        return fn.apply(null,args);
    }
}

//     
function add(x, y){
    return x + y;
}

//      curry          
var newadd = schonfinkelize(add,5);
console.log(newadd(4)); //  9

//
console.log(schonfinkelize(add,6)(7)); //  13

//                  Curry 
//     
function addSome(a, b, c, d, e) {
    return a + b + c + d + e;
}

//            
console.log(schonfinkelize(addSome,1,2,3)(5,5));

//   curry 
var addOne = schonfinkelize(addSome,1);
console.log(addOne(10,10,10,10)); //41
var addSix = schonfinkelize(addOne,2,3);
console.log(addSix(5,5)); // 16
上記は完全な例とテストです.
いつCurry化が適していますか?同じ関数が起動されており、伝達されるパラメータはほとんど同じであることが分かった場合、この関数はCurry化のための優れた候補パラメータであるかもしれない.関数のセット部分を関数に適用することで、新しい関数を動的に作成することができます.この新しい関数は繰り返しパラメータを保存します.したがって、毎回これらのパラメータを渡す必要はありません.また、プレ充填元関数が期待する完全なパラメータリストを使用します.
 
結び目
JavaScriptでは、関数に関する部分が非常に重要です.このシリーズの文章に関する主要な関数の部分はもうここで一段落しました.本論文では関数に関する背景と用語について論じた.JavaScriptの中の二つの重要な特徴を学びました.すなわち、
  • 関数は、第1のクラスのオブジェクトであり、属性および方法を有する値およびパラメータとして伝達され得る.
  • 関数は、ローカルスコープを提供し、他の括弧は、このようなローカルスコープを提供することができない(もちろん、現在のletは可能である).さらに、宣言の局所変数は、局所的な作用領域の上部に引き上げられることができるということを覚えておく必要がある.
  • 関数を作成するシンタックスは以下の通りです.
  • .  関数名式.
  • .関数式(上記と同じですが、名前が一つ足りません.)は、通常は匿名関数とも呼ばれます.
  • .関数宣言は、他の言語の関数の構文と類似している.
  • 関数の背景と文法をカバーした後、いくつかの有用なパターンを学びました.
    1、APIモードでは、関数のためにより良く、よりクリーンなインターフェースを提供することができます.
    コールバックモード:関数をパラメータとして転送します.
    オブジェクトを設定します.制御された関数のパラメータ数を維持するのに役立ちます.
    戻り関数:関数の戻り値が別の関数の場合.
    Curry化:既存の関数に基づいて新しい関数が作成され、一部のパラメータリストが追加された場合.
    2、初期化モードは、グローバル名前空間を汚染しない場合に、一時変数を使って、よりクリーンで構造的な方法で初期化及び設定タスクを実行することができます.これらのモードは以下を含む.
    即時関数:定義したらすぐ実行します.
    インスタントオブジェクト初期化:匿名オブジェクトは初期化タスクを組織し、即時に起動できる方法を提供する.
    初期化時の分岐:ヘルプの分岐コードは初期化コードの実行中に一回だけ検出されます.これは以降プログラムライフサイクル内で複数回検出されるのとは逆です.
    3、性能モードは、コードの運転を加速させることができます.これらのモードは以下を含みます.
    スペル忘れモード:関数属性を使用して、計算した値を再計算する必要がないようにします.
    カスタムモード:新しいメイン体重で自分を書きます.2回目または以降の呼び出し時にはより少ない作業を実行するだけです.
     
    はい、関数の部分はこれで終わります.私たちは次から対象パターンの部分を学びます.頑張って!ファイトリング