อะไรคือความแตกต่างระหว่างการต่อเนื่องและการโทรกลับ?


133

ฉันท่องเว็บไปทั่วเพื่อค้นหาการรู้แจ้งเกี่ยวกับความต่อเนื่องและเป็นเรื่องที่น่าเหลือเชื่อว่าคำอธิบายที่ง่ายที่สุดสามารถสร้างความสับสนให้กับโปรแกรมเมอร์ JavaScript อย่างฉันได้อย่างไร โดยเฉพาะอย่างยิ่งเมื่อบทความส่วนใหญ่อธิบายความต่อเนื่องของโค้ดใน Scheme หรือใช้ monads

ในที่สุดฉันก็คิดว่าฉันเข้าใจสาระสำคัญของความต่อเนื่องแล้วฉันอยากรู้ว่าสิ่งที่ฉันรู้นั้นเป็นความจริงหรือไม่ ถ้าสิ่งที่ฉันคิดว่าเป็นความจริงไม่เป็นความจริงก็เป็นความไม่รู้และไม่ใช่การรู้แจ้ง

นี่คือสิ่งที่ฉันรู้:

ในเกือบทุกภาษาฟังก์ชันจะส่งคืนค่า (และการควบคุม) ไปยังผู้โทรอย่างชัดเจน ตัวอย่างเช่น:

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);
}

ดังนั้นแทนที่จะส่งคืนค่าจากฟังก์ชันเรากำลังดำเนินการต่อด้วยฟังก์ชันอื่น ดังนั้นฟังก์ชันนี้จึงถูกเรียกว่าต่อเนื่องจากฟังก์ชันแรก

อะไรคือความแตกต่างระหว่างการต่อเนื่องและการโทรกลับ?


4
ส่วนหนึ่งของฉันคิดว่านี่เป็นคำถามที่ดีจริงๆและส่วนหนึ่งของฉันคิดว่ามันยาวเกินไปและอาจเป็นเพียงคำตอบที่ 'ใช่ / ไม่ใช่' อย่างไรก็ตามเนื่องจากความพยายามและการวิจัยที่เกี่ยวข้องฉันจะรู้สึกถึงความรู้สึกแรก
Andras Zoltan

2
คำถามของคุณคืออะไร? ดูเหมือนว่าคุณจะเข้าใจเรื่องนี้ดีทีเดียว
Michael Aaron Safyan

3
ใช่ฉันเห็นด้วย - ฉันคิดว่ามันน่าจะเป็นบล็อกโพสต์มากกว่าในบรรทัดของ 'ความต่อเนื่องของ JavaScript - สิ่งที่ฉันเข้าใจว่าเป็น'
Andras Zoltan

9
มีคำถามสำคัญ: "ความต่อเนื่องและการโทรกลับต่างกันอย่างไร" ตามด้วย "ฉันเชื่อว่า ... " คำตอบสำหรับคำถามนั้นอาจน่าสนใจ?
ความสับสน

3
ดูเหมือนว่าอาจมีการโพสต์ที่เหมาะสมกว่าใน programmers.stackexchange.com
Brian Reischl

คำตอบ:


164

ฉันเชื่อว่าความต่อเนื่องเป็นกรณีพิเศษของการโทรกลับ ฟังก์ชันอาจเรียกกลับจำนวนฟังก์ชันจำนวนเท่าใดก็ได้ ตัวอย่างเช่น:

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);
        }
    }
}

หากฟังก์ชันเรียกใช้ฟังก์ชันอื่นเป็นสิ่งสุดท้ายจะเรียกว่าการเรียกหาง บางภาษาเช่น Scheme ทำการเพิ่มประสิทธิภาพการโทรหาง ซึ่งหมายความว่าการเรียกหางจะไม่เกิดค่าใช้จ่ายทั้งหมดของการเรียกฟังก์ชัน แทนที่จะใช้เป็น goto แบบธรรมดา (โดยสแต็กเฟรมของฟังก์ชันการโทรถูกแทนที่ด้วยสแต็กเฟรมของการโทรหาง)

โบนัส : เป็นไปตามรูปแบบการส่งต่ออย่างต่อเนื่อง พิจารณาโปรแกรมต่อไปนี้:

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. การส่งผ่านความต่อเนื่องจะเพิ่มขนาดของ call stack เว้นแต่คุณจะใช้ภาษาเช่น Scheme ซึ่งกำจัดการโทรหางคุณจะเสี่ยงต่อการใช้พื้นที่สแต็กเต็ม
  2. การเขียนฟังก์ชันซ้อนกันเป็นความเจ็บปวด

ปัญหาแรกสามารถแก้ไขได้อย่างง่ายดายใน JavaScript โดยการเรียกใช้ความต่อเนื่องแบบอะซิงโครนัส โดยการเรียกความต่อเนื่องแบบอะซิงโครนัสฟังก์ชันจะส่งกลับก่อนที่จะเรียกใช้ความต่อเนื่อง ดังนั้นขนาดของ call stack จึงไม่เพิ่มขึ้น:

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น่าเสียดายที่callccไม่สามารถใช้งานได้อย่างสมบูรณ์ใน JavaScript แต่เราสามารถเขียนฟังก์ชันทดแทนสำหรับกรณีการใช้งานส่วนใหญ่ได้:

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เป็นฟังก์ชันต่อเนื่องซึ่ง wraps callccขึ้นส่วนที่เหลือของร่างกายทำงานหลังจากที่การเรียกร้องให้

พิจารณาเนื้อหาของฟังก์ชั่นpythagoras:

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

ประการcurrent-continuationที่สองcallccคือ:

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

ในทำนองเดียวกันสิ่งcurrent-continuationแรกcallccคือ:

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

เนื่องจากส่วนcurrent-continuationแรกcallccมีอีกรายการหนึ่งcallccจึงต้องถูกแปลงเป็นรูปแบบการส่งต่อความต่อเนื่อง:

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

ดังนั้นโดยพื้นฐานแล้วcallccเหตุผลที่จะแปลงเนื้อความของฟังก์ชันทั้งหมดกลับไปเป็นสิ่งที่เราเริ่มต้น (และตั้งชื่อฟังก์ชันที่ไม่ระบุตัวตนเหล่านั้นcc) ฟังก์ชัน pythagoras โดยใช้ callcc นี้จะกลายเป็น:

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ใน JavaScript ได้ แต่คุณสามารถใช้รูปแบบการส่งผ่านความต่อเนื่องใน JavaScript ได้ดังนี้:

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นี้สามารถใช้เพื่อปรับใช้โครงสร้างการไหลของการควบคุมที่ซับซ้อนเช่นบล็อกลองจับโครูทีนเครื่องกำเนิดไฟฟ้าเส้นใยฯลฯ


10
ฉันรู้สึกขอบคุณคำพูดที่ไม่สามารถอธิบายได้ ในที่สุดฉันก็เข้าใจแนวคิดที่เกี่ยวข้องกับความต่อเนื่องในระดับสัญชาตญาณในการกวาดครั้งเดียว! ฉันใหม่ทันทีที่คลิกมันจะง่ายและฉันจะเห็นว่าฉันใช้รูปแบบหลายครั้งก่อนที่จะไม่รู้ตัวและมันก็เป็นแบบนั้น ขอบคุณมากสำหรับคำอธิบายที่ยอดเยี่ยมและชัดเจน
ATA

2
Trampolines เป็นสิ่งที่ค่อนข้างเรียบง่าย แต่ทรงพลัง โปรดตรวจสอบโพสต์ของ Reginald Braithwaiteเกี่ยวกับพวกเขา
Marco Faustinelli

1
ขอบคุณสำหรับคำตอบ. ฉันสงสัยว่าคุณสามารถให้การสนับสนุนเพิ่มเติมสำหรับคำสั่งที่ callcc ไม่สามารถใช้งานใน JavaScript ได้หรือไม่? อาจเป็นคำอธิบายว่า JavaScript ต้องใช้อะไร?
John Henry

1
@JohnHenry - จริงๆแล้วมีการใช้งาน call / cc ใน JavaScript ที่ Matt Might ทำ ( matt.might.net/articles/by-example-continuation-passing-style - ไปที่ย่อหน้าสุดท้าย) แต่โปรดอย่า ' อย่าถามฉันว่ามันทำงานอย่างไรหรือใช้อย่างไร :-)
Marco Faustinelli

1
@JohnHenry JS ต้องการความต่อเนื่องระดับเฟิร์สคลาส (คิดว่ามันเป็นกลไกในการจับสถานะบางอย่างของ call stack) แต่มีเพียงฟังก์ชั่นชั้นหนึ่งและการปิดดังนั้น CPS จึงเป็นวิธีเดียวที่จะเลียนแบบความต่อเนื่อง ใน Scheme conts มีความหมายโดยปริยายและหน้าที่ส่วนหนึ่งของ callcc คือการ "reify" โดยนัยเหล่านี้เพื่อให้ฟังก์ชันที่ใช้งานได้เข้าถึงได้ นั่นเป็นเหตุผลที่ callcc ใน Scheme คาดหวังให้ฟังก์ชันเป็นอาร์กิวเมนต์เดียว callcc เวอร์ชัน CPS ใน JS แตกต่างกันเนื่องจาก cont ถูกส่งผ่านเป็นอาร์กิวเมนต์ func ที่ชัดเจน ดังนั้น callcc ของ Aadit จึงเพียงพอสำหรับการใช้งานจำนวนมาก
scriptum

27

แม้จะมีการเขียนที่ยอดเยี่ยม แต่ฉันคิดว่าคุณสับสนคำศัพท์ของคุณเล็กน้อย ตัวอย่างเช่นคุณถูกต้องที่การเรียกหางเกิดขึ้นเมื่อการเรียกเป็นสิ่งสุดท้ายที่ฟังก์ชันต้องดำเนินการ แต่ในส่วนที่เกี่ยวข้องกับความต่อเนื่องการเรียกหางหมายความว่าฟังก์ชันไม่ได้แก้ไขความต่อเนื่องที่เรียกด้วยเพียงอย่างเดียว อัปเดตค่าที่ส่งผ่านไปยังความต่อเนื่อง (หากต้องการ) นี่คือเหตุผลที่การแปลงฟังก์ชัน tail recursive เป็น CPS นั้นง่ายมาก (คุณเพียงแค่เพิ่มความต่อเนื่องเป็นพารามิเตอร์และเรียกใช้ความต่อเนื่องในผลลัพธ์)

นอกจากนี้ยังเป็นเรื่องแปลกเล็กน้อยที่จะเรียกความต่อเนื่องเป็นกรณีพิเศษของการโทรกลับ ฉันเห็นวิธีที่พวกเขารวมกลุ่มกันได้อย่างง่ายดาย แต่ความต่อเนื่องไม่ได้เกิดขึ้นจากความจำเป็นในการแยกแยะจากการโทรกลับ ความต่อเนื่องแสดงถึงคำสั่งที่เหลืออยู่ในการคำนวณให้สมบูรณ์หรือส่วนที่เหลือของการคำนวณจากจุดนี้ในช่วงเวลานี้ คุณสามารถคิดว่าความต่อเนื่องเป็นช่องว่างที่ต้องกรอกหากฉันสามารถจับความต่อเนื่องปัจจุบันของโปรแกรมได้ฉันก็จะย้อนกลับไปว่าโปรแกรมเป็นอย่างไรเมื่อฉันจับความต่อเนื่อง (นั่นทำให้ผู้ดีบั๊กเขียนง่ายขึ้น)

ในบริบทนี้คำตอบสำหรับคำถามของคุณคือการโทรกลับเป็นสิ่งทั่วไปที่ถูกเรียกเมื่อใดก็ได้ตามที่สัญญาระบุไว้โดยผู้โทร [ของการโทรกลับ] การเรียกกลับสามารถมีอาร์กิวเมนต์ได้มากเท่าที่ต้องการและมีโครงสร้างในแบบที่ต้องการ ดังนั้นความต่อเนื่องจำเป็นต้องเป็นกระบวนงานอาร์กิวเมนต์เดียวที่แก้ไขค่าที่ส่งผ่านเข้าไป ต้องใช้ความต่อเนื่องกับค่าเดียวและแอปพลิเคชันจะต้องเกิดขึ้นในตอนท้าย เมื่อการดำเนินการต่อเนื่องเสร็จสิ้นการดำเนินการนิพจน์จะเสร็จสมบูรณ์และขึ้นอยู่กับความหมายของภาษาอาจมีการสร้างผลข้างเคียงหรือไม่ก็ได้


3
ขอบคุณสำหรับคำชี้แจง คุณถูกต้อง ความต่อเนื่องคือการทำให้สถานะการควบคุมของโปรแกรมกลับมาใหม่ซึ่งเป็นภาพรวมของสถานะของโปรแกรม ณ ช่วงเวลาหนึ่ง การที่จะเรียกเหมือนฟังก์ชั่นปกตินั้นไม่เกี่ยว ความต่อเนื่องไม่ใช่หน้าที่จริง ในทางกลับกันการโทรกลับเป็นหน้าที่จริงๆ นั่นคือความแตกต่างที่แท้จริงระหว่างการต่อเนื่องและการเรียกกลับ อย่างไรก็ตาม JS ไม่รองรับการต่อเนื่องระดับเฟิร์สคลาส ฟังก์ชั่นชั้นหนึ่งเท่านั้น ดังนั้นความต่อเนื่องที่เขียนด้วย CPS ใน JS จึงเป็นเพียงฟังก์ชัน ขอบคุณสำหรับข้อมูลของคุณ =)
Aadit M Shah

4
@AaditMShah ใช่ฉันคิดถึงตรงนั้น ความต่อเนื่องไม่จำเป็นต้องเป็นฟังก์ชัน (หรือขั้นตอนตามที่ฉันเรียกมัน) ตามความหมายมันเป็นเพียงการแสดงนามธรรมของสิ่งต่างๆที่ยังไม่มา อย่างไรก็ตามแม้ใน Scheme จะมีการเรียกใช้ความต่อเนื่องเหมือนโพรซีเดอร์และส่งต่อเป็นหนึ่งเดียว อืม .. นี่ทำให้เกิดคำถามที่น่าสนใจไม่แพ้กันว่าการต่อเนื่องที่ดูเหมือนว่าไม่ใช่ฟังก์ชัน / ขั้นตอน
dcow

@AaditMShah น่าสนใจพอที่ฉันจะพูดคุยต่อที่นี่: programmers.stackexchange.com/questions/212057/…
dcow

14

คำตอบสั้น ๆ คือความแตกต่างระหว่างการต่อเนื่องและการเรียกกลับคือหลังจากเรียกการเรียกกลับ (และเสร็จสิ้น) การเรียกกลับจะดำเนินการต่อ ณ จุดที่ถูกเรียกในขณะที่การเรียกใช้ความต่อเนื่องจะทำให้การดำเนินการดำเนินการต่อ ณ จุดที่สร้างความต่อเนื่อง ในคำอื่น ๆ : ความต่อเนื่องไม่เคยผลตอบแทน

พิจารณาฟังก์ชั่น:

function add(x, y, c) {
    alert("before");
    c(x+y);
    alert("after");
}

(ฉันใช้ไวยากรณ์ Javascript แม้ว่า Javascript จะไม่รองรับความต่อเนื่องชั้นหนึ่งเพราะนี่คือสิ่งที่คุณยกตัวอย่างมาและคนที่ไม่คุ้นเคยกับไวยากรณ์ Lisp จะเข้าใจได้มากกว่า)

ตอนนี้ถ้าเราส่งการติดต่อกลับ:

add(2, 3, function (sum) {
    alert(sum);
});

จากนั้นเราจะเห็นการแจ้งเตือนสามรายการ: "before", "5" และ "after"

ในทางกลับกันถ้าเราจะส่งต่อความต่อเนื่องที่ทำเช่นเดียวกับการโทรกลับเช่นนี้:

alert(callcc(function(cc) {
    add(2, 3, cc);
}));

จากนั้นเราจะเห็นการแจ้งเตือนเพียงสองครั้ง: "ก่อน" และ "5" การเรียกc()เข้าไปข้างในadd()สิ้นสุดการดำเนินการadd()และทำให้เกิดcallcc()การส่งคืน ค่าที่ส่งกลับมาcallcc()คือค่าที่ส่งผ่านเป็นอาร์กิวเมนต์ถึงc(กล่าวคือผลรวม)

ในแง่นี้แม้ว่าการเรียกใช้ความต่อเนื่องจะดูเหมือนการเรียกใช้ฟังก์ชัน แต่ก็มีลักษณะคล้ายกับคำสั่ง return หรือการทิ้งข้อยกเว้น

ในความเป็นจริง call / cc สามารถใช้เพื่อเพิ่มคำสั่ง return ในภาษาที่ไม่รองรับได้ ตัวอย่างเช่นหาก JavaScript ไม่มีคำสั่ง return (เช่นเดียวกับภาษา Lips หลายภาษาเพียงแค่ส่งคืนค่าของนิพจน์สุดท้ายในเนื้อหาฟังก์ชัน) แต่มี call / cc เราสามารถใช้ return ดังนี้:

function find(myArray, target) {
    callcc(function(return) {
        var i;
        for (i = 0; i < myArray.length; i += 1) {
            if(myArray[i] === target) {
                return(i);
            }
        }
        return(undefined); // Not found.
    });
}

โทรreturn(i)จะเรียกความต่อเนื่องที่สิ้นสุดการดำเนินการของฟังก์ชั่นที่ไม่ระบุชื่อและทำให้เกิดการcallcc()ที่จะกลับดัชนีiที่ถูกพบในtargetmyArray

(หมายเหตุ: มีบางวิธีที่การเปรียบเทียบ "return" ค่อนข้างง่ายตัวอย่างเช่นหากความต่อเนื่องหลุดออกจากฟังก์ชันที่สร้างขึ้น - โดยการบันทึกไว้ในที่ใดที่หนึ่งทั่วโลก - เป็นไปได้ว่าฟังก์ชัน ที่สร้างความต่อเนื่องสามารถส่งคืนได้หลายครั้งแม้ว่าจะถูกเรียกใช้เพียงครั้งเดียว )

Call / cc สามารถใช้ในการจัดการข้อยกเว้น (โยนและลอง / จับ) ลูปและโครงสร้างการควบคุมอื่น ๆ ในทำนองเดียวกัน

เพื่อล้างความเข้าใจผิดที่อาจเกิดขึ้น:

  • การเพิ่มประสิทธิภาพการโทรหางไม่จำเป็นด้วยวิธีการใด ๆ เพื่อรองรับการต่อเนื่องระดับเฟิร์สคลาส พิจารณาว่าแม้แต่ภาษาซีก็มีรูปแบบการต่อเนื่อง (จำกัด ) ในรูปแบบsetjmp()ซึ่งสร้างความต่อเนื่องและlongjmp()ซึ่งเรียกใช้!

    • ในทางกลับกันถ้าคุณพยายามเขียนโปรแกรมของคุณอย่างไร้เดียงสาในรูปแบบการส่งต่อแบบต่อเนื่องโดยไม่มีการเพิ่มประสิทธิภาพการโทรหางคุณจะถึงวาระที่จะล้นสแต็กในที่สุด
  • ไม่มีเหตุผลใดเป็นพิเศษที่ความต่อเนื่องจำเป็นต้องใช้ข้อโต้แย้งเพียงข้อเดียว เป็นเพียงอาร์กิวเมนต์ที่ทำให้เกิดความต่อเนื่องกลายเป็นค่าส่งคืนของการโทร / cc และโดยทั่วไปการโทร / cc จะถูกกำหนดให้มีค่าส่งคืนเดียวดังนั้นโดยธรรมชาติแล้วความต่อเนื่องจะต้องใช้ค่าเดียว ในภาษาที่รองรับค่าการส่งคืนหลายค่า (เช่น Common Lisp, Go หรือ Scheme) จะมีความต่อเนื่องที่ยอมรับหลายค่าได้


2
ขออภัยหากฉันทำผิดพลาดในตัวอย่าง JavaScript การเขียนคำตอบนี้ได้เพิ่มจำนวน JavaScript ทั้งหมดที่ฉันเขียนเป็นสองเท่าโดยประมาณ
cpcallen

ฉันเข้าใจถูกต้องหรือไม่ว่าคุณกำลังพูดถึงความต่อเนื่องที่ไม่ จำกัด ในคำตอบนี้และคำตอบที่ยอมรับนั้นพูดถึงความต่อเนื่องที่คั่นด้วย
Jozef Mikušinec

1
"การเรียกใช้ต่อเนื่องทำให้เกิดการดำเนินการเพื่อกลับมาที่จุดต่อเนื่องที่ถูกสร้างขึ้น" - ฉันคิดว่าคุณมีความสับสน "สร้าง" ความต่อเนื่องกับการจับภาพต่อเนื่องในปัจจุบัน
Alexey

@ Alexey: นี่เป็นประเภทของการอวดรู้ที่ฉันเห็นด้วย แต่ภาษาส่วนใหญ่ไม่มีวิธีใดในการสร้างความต่อเนื่อง (reified) นอกเหนือจากการจับความต่อเนื่องในปัจจุบัน
cpcallen

1
@jozef: ฉันกำลังพูดถึงความต่อเนื่องที่ไม่ จำกัด ฉันคิดว่านั่นเป็นความตั้งใจของ Aadit เช่นกันแม้ว่าในขณะที่ dcow บันทึกคำตอบที่ยอมรับนั้นล้มเหลวในการแยกแยะความต่อเนื่องจากการเรียกหาง (ที่เกี่ยวข้องอย่างใกล้ชิด) และฉันสังเกตว่าการต่อเนื่องที่คั่นจะเทียบเท่ากับ fuction / procedure อย่างไรก็ตาม: community.schemewiki.org/ ? composable-
continuousations
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.