javascript - निरंतरता और कॉलबैक के बीच क्या अंतर है?




continuations callcc (2)

अद्भुत लेखन के बावजूद, मुझे लगता है कि आप अपनी शब्दावली को थोड़ा उलझन में डाल रहे हैं। उदाहरण के लिए, आप सही हैं कि एक पूंछ कॉल तब होती है जब कॉल अंतिम कार्य को निष्पादित करने की आवश्यकता होती है, लेकिन निरंतरता के संबंध में, पूंछ कॉल का अर्थ है कि फ़ंक्शन उस निरंतरता को संशोधित नहीं करता है जिसे इसे कहा जाता है, केवल यही निरंतरता (यदि यह चाहता है) को पारित मूल्य अद्यतन करता है। यही कारण है कि एक पूंछ रिकर्सिव फ़ंक्शन को सीपीएस में परिवर्तित करना इतना आसान है (आप केवल पैरामीटर के रूप में निरंतरता जोड़ते हैं और परिणाम पर निरंतरता को कॉल करते हैं)।

कॉलबैक के एक विशेष मामले को जारी रखने के लिए यह थोड़ा अजीब भी है। मैं देख सकता हूं कि उन्हें आसानी से कैसे समूहीकृत किया जाता है, लेकिन कॉलबैक से अलग होने की आवश्यकता से निरंतरता उत्पन्न नहीं होती है। एक निरंतरता वास्तव में एक गणना को पूरा करने के लिए शेष निर्देशों का प्रतिनिधित्व करती है, या समय पर इस बिंदु से गणना का शेष। आप एक छेद के रूप में एक छेद के रूप में सोच सकते हैं जिसे भरने की जरूरत है। अगर मैं किसी प्रोग्राम की वर्तमान निरंतरता को कैप्चर कर सकता हूं, तो मैं निरंतर कब्जा कर लिया था जब प्रोग्राम निरंतरता पर कब्जा कर लिया था। (यह सुनिश्चित करने के लिए डिबगर्स को लिखना आसान बनाता है।)

इस संदर्भ में, आपके प्रश्न का उत्तर यह है कि कॉलबैक एक सामान्य चीज है जिसे कॉलर [कॉलबैक के] द्वारा प्रदान किए गए कुछ अनुबंध द्वारा निर्दिष्ट समय पर किसी भी समय बुलाया जाता है। एक कॉलबैक में जितना चाहें उतने तर्क हो सकते हैं और किसी भी तरह से संरचित किया जा सकता है। एक निरंतरता , फिर, एक तर्क प्रक्रिया है जो उसमें पारित मूल्य को हल करती है। एक निरंतरता को एक मूल्य पर लागू किया जाना चाहिए और अंत में आवेदन होना चाहिए। जब अभिव्यक्ति को निष्पादित करने की निरंतरता पूर्ण हो जाती है, और, भाषा के अर्थशास्त्र के आधार पर, दुष्प्रभाव उत्पन्न हो सकते हैं या नहीं भी हो सकते हैं।

मैं निरंतरता के बारे में ज्ञान की खोज में पूरे वेब पर ब्राउज़ कर रहा हूं, और यह दिमागी दबदबा है कि स्पष्टीकरण का सबसे सरल कैसे एक जावास्क्रिप्ट प्रोग्रामर को अपने आप को भ्रमित कर सकता है। यह विशेष रूप से सच है जब अधिकांश लेख योजना में कोड के साथ निरंतरता की व्याख्या करते हैं या मोनैड का उपयोग करते हैं।

अब जब मैं अंततः सोचता हूं कि मैं निरंतरता के सार को समझ गया हूं, मैं जानना चाहता हूं कि मुझे क्या पता है कि वास्तव में सच है। यदि मुझे लगता है कि सत्य सच है तो वास्तव में सत्य नहीं है, तो यह अज्ञान है और ज्ञान नहीं है।

तो, यहां मुझे क्या पता है:

लगभग सभी भाषाओं में कार्य स्पष्ट रूप से उनके कॉलर को मूल्य (और नियंत्रण) लौटाते हैं। उदाहरण के लिए:

var sum = add(2, 3);

console.log(sum);

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

अब प्रथम श्रेणी के कार्यों वाली भाषा में हम कॉलर पर स्पष्ट रूप से लौटने की बजाय कॉलबैक पर नियंत्रण और वापसी मूल्य पारित कर सकते हैं:

add(2, 3, function (sum) {
    console.log(sum);
});

function add(x, y, cont) {
    cont(x + y);
}

इस प्रकार किसी फ़ंक्शन से मूल्य लौटने की बजाय हम दूसरे फ़ंक्शन के साथ आगे बढ़ रहे हैं। इसलिए इस समारोह को पहले की निरंतरता कहा जाता है।

तो निरंतरता और कॉलबैक के बीच क्या अंतर है?


मेरा मानना ​​है कि निरंतरता कॉलबैक का एक विशेष मामला है। एक फ़ंक्शन किसी भी संख्या में कई बार कॉलबैक कॉलबैक कर सकता है। उदाहरण के लिए:

var array = [1, 2, 3];

forEach(array, function (element, array, index) {
    array[index] = 2 * element;
});

console.log(array);

function forEach(array, callback) {
    var length = array.length;
    for (var i = 0; i < length; i++)
        callback(array[i], array, i);
}

हालांकि यदि कोई फ़ंक्शन किसी अन्य फ़ंक्शन को आखिरी चीज़ के रूप में वापस कॉल करता है तो दूसरे फ़ंक्शन को पहले की निरंतरता कहा जाता है। उदाहरण के लिए:

var array = [1, 2, 3];

forEach(array, function (element, array, index) {
    array[index] = 2 * element;
});

console.log(array);

function forEach(array, callback) {
    var length = array.length;

    // This is the last thing forEach does
    // cont is a continuation of forEach
    cont(0);

    function cont(index) {
        if (index < length) {
            callback(array[index], array, index);
            // This is the last thing cont does
            // cont is a continuation of itself
            cont(++index);
        }
    }
}

यदि कोई फ़ंक्शन किसी अन्य फ़ंक्शन को आखिरी चीज़ के रूप में कॉल करता है तो उसे पूंछ कॉल कहा जाता है। योजना जैसी कुछ भाषाएं पूंछ कॉल अनुकूलन करती हैं। इसका मतलब है कि पूंछ कॉल फ़ंक्शन कॉल के पूर्ण ओवरहेड को नहीं लेता है। इसके बजाए इसे एक सरल गोटो के रूप में कार्यान्वित किया गया है (कॉलिंग फ़ंक्शन के ढेर फ्रेम के साथ पूंछ कॉल के ढेर फ्रेम द्वारा प्रतिस्थापित किया गया है)।

बोनस : निरंतर उत्तीर्ण शैली के लिए आगे बढ़ना। निम्नलिखित कार्यक्रम पर विचार करें:

console.log(pythagoras(3, 4));

function pythagoras(x, y) {
    return x * x + y * y;
}

अब यदि प्रत्येक ऑपरेशन (अतिरिक्त, गुणा, इत्यादि सहित) कार्यों के रूप में लिखा गया था तो हमारे पास होगा:

console.log(pythagoras(3, 4));

function pythagoras(x, y) {
    return add(square(x), square(y));
}

function square(x) {
    return multiply(x, x);
}

function multiply(x, y) {
    return x * y;
}

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

इसके अलावा अगर हमें किसी भी मूल्य को वापस करने की अनुमति नहीं थी तो हमें निरंतरता का उपयोग निम्नानुसार करना होगा:

pythagoras(3, 4, console.log);

function pythagoras(x, y, cont) {
    square(x, function (x_squared) {
        square(y, function (y_squared) {
            add(x_squared, y_squared, cont);
        });
    });
}

function square(x, cont) {
    multiply(x, x, cont);
}

function multiply(x, y, cont) {
    cont(x * y);
}

function add(x, y, cont) {
    cont(x + y);
}

प्रोग्रामिंग की यह शैली जिसमें आपको मूल्यों को वापस करने की अनुमति नहीं है (और इसलिए आपको निरंतर निरंतरता का सहारा लेना होगा) निरंतर उत्तीर्ण शैली कहा जाता है।

हालांकि निरंतर उत्तीर्ण शैली के साथ दो समस्याएं हैं:

  1. निरंतरता के आसपास गुजरने से कॉल स्टैक का आकार बढ़ जाता है। जब तक आप योजना जैसी भाषा का उपयोग नहीं कर रहे हैं जो पूंछ कॉल को समाप्त करता है तो आपको स्टैक स्पेस से बाहर होने का खतरा होगा।
  2. नेस्टेड कार्यों को लिखना दर्द है।

निरंतरता को अतुल्यकालिक रूप से कॉल करके पहली समस्या आसानी से जावास्क्रिप्ट में हल की जा सकती है। निरंतरता को निरंतर कॉल करने से पहले फ़ंक्शन रिटर्न को निरंतर कॉल करके कॉल किया जाता है। इसलिए कॉल स्टैक आकार में वृद्धि नहीं होती है:

Function.prototype.async = async;

pythagoras.async(3, 4, console.log);

function pythagoras(x, y, cont) {
    square.async(x, function (x_squared) {
        square.async(y, function (y_squared) {
            add.async(x_squared, y_squared, cont);
        });
    });
}

function square(x, cont) {
    multiply.async(x, x, cont);
}

function multiply(x, y, cont) {
    cont.async(x * y);
}

function add(x, y, cont) {
    cont.async(x + y);
}

function async() {
    setTimeout.bind(null, this, 0).apply(null, arguments);
}

दूसरी समस्या आमतौर पर call-with-current-continuation callcc call-with-current-continuation नामक फ़ंक्शन का उपयोग करके हल की जाती है जिसे अक्सर callcc रूप में संक्षिप्त किया callcc । दुर्भाग्यवश जावास्क्रिप्ट को पूरी तरह से जावास्क्रिप्ट में लागू नहीं किया जा सकता है, लेकिन हम इसके अधिकांश उपयोग मामलों के लिए एक प्रतिस्थापन कार्य लिख सकते हैं:

pythagoras(3, 4, console.log);

function pythagoras(x, y, cont) {
    var x_squared = callcc(square.bind(null, x));
    var y_squared = callcc(square.bind(null, y));
    add(x_squared, y_squared, cont);
}

function square(x, cont) {
    multiply(x, x, cont);
}

function multiply(x, y, cont) {
    cont(x * y);
}

function add(x, y, cont) {
    cont(x + y);
}

function callcc(f) {
    var cc = function (x) {
        cc = x;
    };

    f(cc);

    return cc;
}

callcc फ़ंक्शन एक फ़ंक्शन f लेता है और इसे current-continuation ( cc रूप में संक्षेप में) पर लागू करता है। current-continuation निरंतर कार्य है जो callcc के कॉल के बाद शेष फ़ंक्शन बॉडी को callcc

समारोह pythagoras के शरीर पर विचार करें:

var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);

दूसरी callcc की current-continuation है:

function cc(y_squared) {
    add(x_squared, y_squared, cont);
}

इसी तरह पहली callcc की current-continuation है:

function cc(x_squared) {
    var y_squared = callcc(square.bind(null, y));
    add(x_squared, y_squared, cont);
}

चूंकि पहली callcc की current-continuation में एक और callcc इसे निरंतर उत्तीर्ण शैली में परिवर्तित किया जाना चाहिए:

function cc(x_squared) {
    square(y, function cc(y_squared) {
        add(x_squared, y_squared, cont);
    });
}

इसलिए अनिवार्य रूप से callcc रूप से पूरे फ़ंक्शन बॉडी को जो हमने शुरू किया है callcc परिवर्तित करता है (और उन अनाम कार्यों को नाम cc )। कॉलक के इस कार्यान्वयन का उपयोग करते हुए पाइथागोरस फ़ंक्शन तब होता है:

function pythagoras(x, y, cont) {
    callcc(function(cc) {
        square(x, function (x_squared) {
            square(y, function (y_squared) {
                add(x_squared, y_squared, cont);
            });
        });
    });
}

फिर आप जावास्क्रिप्ट में callcc को कार्यान्वित नहीं कर सकते हैं, लेकिन आप इसे जावास्क्रिप्ट में जारी रखने की शैली को निम्नानुसार कार्यान्वित कर सकते हैं:

Function.prototype.async = async;

pythagoras.async(3, 4, console.log);

function pythagoras(x, y, cont) {
    callcc.async(square.bind(null, x), function cc(x_squared) {
        callcc.async(square.bind(null, y), function cc(y_squared) {
            add.async(x_squared, y_squared, cont);
        });
    });
}

function square(x, cont) {
    multiply.async(x, x, cont);
}

function multiply(x, y, cont) {
    cont.async(x * y);
}

function add(x, y, cont) {
    cont.async(x + y);
}

function async() {
    setTimeout.bind(null, this, 0).apply(null, arguments);
}

function callcc(f, cc) {
    f.async(cc);
}

फ़ंक्शन callcc का उपयोग जटिल नियंत्रण प्रवाह संरचनाओं जैसे कि callcc -कैच ब्लॉक, कोरआउट, जेनरेटर, fibers इत्यादि को लागू करने के लिए किया जा सकता है।







continuation-passing