ทำไมเราต้องการ“ ฟังก์ชั่นโทรกลับ”?


16

programming in Luaฉันกำลังอ่านหนังสือ มันบอกว่า

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

function digitButton (digit)
  return Button{label = tostring(digit),
                action = function ()
                  add_to_display(digit)
                  end}
end

มันดูเหมือนว่าถ้าผมเรียกdigitButtonมันจะกลับaction(นี้จะสร้างปิด) ดังนั้นฉันสามารถเข้าถึงผ่านไปdigitdigitButton

คำถามของฉันคือ:

Why we need call back functions? what situations can I apply this to?


ผู้เขียนกล่าวว่า:

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

ตามผู้เขียนฉันคิดว่าตัวอย่างที่คล้ายกันเป็นเช่นนี้:

function Button(t)
  -- maybe you should set the button here
  return t.action -- so that you can call this later
end

function add_to_display(digit)
  print ("Display the button label: " .. tostring(digit))
end

function digitButton(digit)
  return Button{label = tostring(digit),
                action = function ()
                           add_to_display(digit)
                           end}
end

click_action = digitButton(10)
click_action()

ดังนั้น, the callback can be called a long time after digitButton did its task and after the local variable digit went out of scope.


คำตอบ:


45

Guy 1 to Guy 2: เฮ้เพื่อนฉันต้องการทำอะไรบางอย่างเมื่อผู้ใช้คลิกที่นั่นโทรหาฉันกลับเมื่อเกิดอะไรขึ้น

Guy 2 โทรกลับ Guy 1 เมื่อผู้ใช้คลิกที่นี่


1
ฉันชอบตลกการโทรกลับนี้มาก :-P
Lucas Li

6
มันเป็นแค่ฉันเหรอ? ฉันไม่เข้าใจวิธีนี้อธิบายว่าทำไมเราต้องการฟังก์ชันการโทรกลับ
phant0m

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

นี่คือคำอธิบายที่น่ากลัว
insidesin

21

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


ฉันไม่เห็นด้วยกับคุณ ฉันแก้ไขโพสต์และเพิ่มรหัสแล้ว ฉันยังคิดว่าการกระทำนั้นไม่ได้ดำเนินการทันที
Lucas Li

2
ทิมคุณพูดถูก แต่ก็คือ ม.ค. "ปุ่มนี้จะทำงานเมื่อผู้ใช้คลิกที่ปุ่ม" ไม่ใช่ในทันที
AlexanderBrevig

@AlexanderBrevig ใช่ฉันยังคงคิดว่าคำตอบของแจนมีประโยชน์มากสำหรับฉัน มันทำให้ฉันรู้ว่าอะไรคือสิ่งที่โทรกลับและทำไมเราต้องโทรกลับ
Lucas Li

การโทรกลับเป็นเพียงชื่อสกปรกที่มีคนเพิ่มลงในฟังก์ชั่นที่สองที่ทำงานเมื่อแรกถูกไล่ออกและอาจขึ้นอยู่กับเกณฑ์ เรียกใช้ฟังก์ชันของคุณ well_done () และจะไม่มีความแตกต่าง การติดต่อกลับเป็นเหมือนกับ Verify, Verify เป็นลำดับข้อความโดยปกติจะเป็นสองข้อความและการติดต่อกลับเป็นความปลอดภัยของฟังก์ชัน หากคุณถูก จำกัด การโทรกลับ 3 ครั้งขอแสดงความยินดีคุณต้องการทำสิ่งต่าง ๆ อย่างหนัก
m3nda

16

ฉันคิดว่าตัวอย่างที่ดีกว่าสำหรับการใช้ callbacks อยู่ในฟังก์ชันแบบอะซิงโครนัส ฉันไม่สามารถพูด Lua ได้ แต่ใน JavaScript หนึ่งในการดำเนินการแบบอะซิงโครนัสที่พบมากที่สุดคือการติดต่อกับเซิร์ฟเวอร์ผ่าน AJAX

การโทร AJAX ไม่ตรงกันหมายความว่าถ้าคุณทำสิ่งนี้:

var ajax = ajaxCall();
if(ajax == true) {
  // Do something
}

เนื้อหาของifบล็อกจะไม่สามารถทำงานได้อย่างถูกต้องและเมื่อมันajaxCall()เกิดขึ้นมันเป็นเพราะฟังก์ชั่นที่เกิดขึ้นจะเสร็จสิ้นก่อนที่การดำเนินการจะมาถึงifคำสั่ง

อย่างไรก็ตามการโทรกลับช่วยขจัดปัญหานี้โดยการทำให้มั่นใจว่าการโทรแบบอะซิงโครนัสเสร็จสิ้นก่อนที่จะเรียกใช้ฟังก์ชันที่จำเป็น ดังนั้นรหัสของคุณจะเปลี่ยนเป็นดังนี้:

function ajaxCallback(response) {   
  if(response == true) {
    // Do something   
  }
}

ajaxCall(ajaxCallback);

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

หากต้องการดูสิ่งนี้ในเชิงปฏิบัติคุณเพียงแค่ต้องดูเว็บไซต์ใด ๆ ที่มีการอัปเดตใด ๆ โดยไม่ต้องโหลดซ้ำทั้งหน้า Twitter, LinkedIn และไซต์ StackExchange เป็นตัวอย่างที่ดี ฟีด "การเลื่อนอย่างไม่มีที่สิ้นสุด" ของ Twitter และการแจ้งเตือนของ SE (สำหรับทั้งการแจ้งเตือนผู้ใช้และการแจ้งเตือนว่าคำถามมีกิจกรรมใหม่) ขับเคลื่อนโดยการโทรแบบอะซิงโครนัส เมื่อคุณเห็นสปินเนอร์ที่ไหนสักแห่งนั่นหมายความว่าส่วนนั้นได้ทำการโทรแบบอะซิงโครนัสและกำลังรอให้การโทรนั้นสิ้นสุด แต่คุณสามารถโต้ตอบกับสิ่งอื่นบนอินเทอร์เฟซ (และแม้แต่การโทรแบบอะซิงโครนัสอื่น ๆ ) เมื่อการโทรเสร็จสิ้นจะมีการตอบสนองและอัปเดตอินเทอร์เฟซตามลำดับ


12

คุณถามคำถาม

ทำไมเราต้องการ "ฟังก์ชั่นโทรกลับ"?

ฉันจะพยายามตอบอย่างกระชับและชัดเจน (หากฉันล้มเหลวโปรดอ้างอิงลิงค์ wiki ที่ด้านล่างของคำตอบนี้)

การเรียกกลับถูกใช้เพื่อเลื่อนการใช้งานบางอย่างไปเป็นช่วงเวลาสุดท้ายที่เป็นไปได้

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

โชคดีที่เรามีความคิดของการเรียกกลับ (inheritence และการใช้งานของรูปแบบแม่แบบยังเป็นชนิดของการเรียกกลับความหมายก) ดังนั้นเราจึงไม่จำเป็นต้องทำอย่างนั้น

แทนที่จะนำกลับมาใช้ใหม่ทุกแง่มุมของปุ่มสิ่งที่เราทำคือการเพิ่มการใช้งานปุ่ม Buttonมีแนวคิดในการทำบางสิ่งบางอย่างเมื่อมันกด เราแค่บอกว่ามันคืออะไร

กลไกการฉีดพฤติกรรมเข้าสู่กรอบเรียกว่าการเรียกกลับ

ตัวอย่าง:

มาฉีดพฤติกรรมบางอย่างลงในปุ่ม HTML:

<button onclick="print()">Print page through a callback to the print() method</button>

ในตัวอย่างนี้onclickชี้ไปที่การโทรกลับซึ่งสำหรับตัวอย่างนี้เป็นprint()วิธีการ


http://en.wikipedia.org/wiki/Callback_(computer_programming)


ขอบคุณหลังจากอ่านภาพประกอบฉันไปอ่านวิกิที่มีตัวอย่างเป็นเพียงการโทรกลับแบบซิงโครนัส
Lucas Li

บอกฟังก์ชั่นคำตอบทุกครั้งที่มีปัญหาและคุณต้องฟังปัญหา สอนฟังก์ชั่นเพื่อแก้ปัญหาที่ต้องดูแลตัวเอง
โจ

8

ทำไมเราต้องใช้ฟังก์ชั่นโทรกลับ? ฉันสามารถใช้สถานการณ์นี้กับอะไรได้บ้าง

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


1
+ นั่นคือสิ่งที่ดีสำหรับการโทรกลับ แต่ฉันเกลียดการเขียน :-)
Mike Dunlavey

เมื่อตอนที่ฉันยังเป็นน้องใหม่คุณครูของฉันสอนเราถึงวิธีการโปรแกรมใน MFC แต่เขาไม่เคยบอกเราว่าการโทรกลับคืออะไร :-(
Lucas Li

2

จะเกิดอะไรขึ้นหากไม่มีการเรียกกลับ:

Guy 1: โอเคฉันรอสิ่งนั้นเกิดขึ้น (ผิวปากนิ้วหัวแม่มือ twiddles)

Guy 2: Dammit! ทำไม Guy 1 ถึงไม่แสดงข้อมูลให้ฉัน? ฉันต้องการที่จะเห็นสิ่งที่เกิดขึ้น !! ของฉันอยู่ที่ไหน มีใครบ้างที่ควรจะทำทุกสิ่งที่นี่


1

Firstoff คำว่าการเรียกกลับหมายถึงการปิดที่ใช้สำหรับบางสิ่ง

ตัวอย่างเช่นสมมติว่าคุณสร้างฟังก์ชั่นการปิดและเก็บไว้ในตัวแปร มันไม่ได้เป็นโทรกลับเพราะมันไม่ได้ถูกใช้เพื่ออะไร

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

โดยปกติแล้วการโทรกลับจะถูกสร้างขึ้นโดยส่วนต่าง ๆ ของโปรแกรมมากกว่าส่วนที่เรียกพวกเขา ดังนั้นจึงง่ายที่จะจินตนาการว่าบางส่วนของโปรแกรมกำลัง "เรียกคืน" ส่วนอื่น ๆ

พูดง่ายๆคือให้การเรียกกลับส่วนหนึ่งของโปรแกรมบอกส่วนอื่นให้ทำบางสิ่ง (อะไร) เมื่อมีอะไรเกิดขึ้น

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


ใช่Extending the lifetime of local variablesก็จะพูดคำupvalueว่า
Lucas Li

อืมน่าสนใจ คำที่มีค่าสูงสุดดูเหมือนจะเฉพาะเจาะจงกับลัวะถึงแม้ว่าการค้นหาโดย Google อย่างรวดเร็วแสดงให้เห็นว่าบางครั้งมันถูกใช้เพื่ออธิบายภาษาอื่น ๆ ฉันจะเพิ่มเข้าไปในคำตอบของฉัน
Jonathan Graef

1

การโทรกลับมีมาตั้งแต่สมัยแรก ๆ ของ Fortran อย่างน้อยที่สุด ตัวอย่างเช่นหากคุณมีตัวแก้ปัญหา ODE (สมการเชิงอนุพันธ์สามัญ) เช่นตัวแก้ Runge-Kutta มันอาจมีลักษณะเช่นนี้:

subroutine rungekutta(neq, t, tend, y, deriv)
  integer neq             ! number of differential equations
  double precision t      ! initial time
  double precision tend   ! final time
  double precision y(neq) ! state vector
  ! deriv is the callback function to evaluate the state derivative
  ...
  deriv(neq, t, y, yprime) ! call deriv to get the state derivative
  ...
  deriv(neq, t, y, yprime) ! call it several times
  ...
end subroutine

ช่วยให้ผู้โทรสามารถปรับแต่งพฤติกรรมของรูทีนย่อยโดยส่งผ่านฟังก์ชั่นวัตถุประสงค์พิเศษที่จะเรียกเมื่อจำเป็น สิ่งนี้ทำให้สามารถใช้ ODE Solver เพื่อจำลองระบบที่หลากหลาย


1

สะดุดกับสิ่งนี้และต้องการให้คำตอบที่ทันสมัยมากขึ้น ...

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

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

รหัสถูกนำมาจาก ... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

หวังว่านี่จะช่วยให้ใครบางคน นี่คือสิ่งที่ช่วยให้ฉันเข้าใจการโทรกลับ :)


1
สิ่งนี้ดูเหมือนจะไม่นำเสนออะไรมากมายเกินกว่าที่ทำคะแนนและอธิบายไว้ในคำตอบก่อน 9 ข้อ
gnat

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

-2

ฉันไม่ทราบ lua แต่โดยทั่วไปแล้ววิธีการโทรกลับบางอย่างเช่นมัลติเธรด ตัวอย่างเช่นในการเขียนโปรแกรมการพัฒนาแอปพลิเคชันมือถือแอปพลิเคชันส่วนใหญ่ทำงานเหมือนการส่งคำขอไปยังเซิร์ฟเวอร์และการเล่นกับ UI โดยมีข้อมูลมาจากการตอบสนองจากเซิร์ฟเวอร์ เมื่อผู้ใช้ส่งคำขอไปยังเซิร์ฟเวอร์จะต้องใช้เวลาในการรับการตอบสนองจากเซิร์ฟเวอร์ แต่สำหรับ UX UI ที่ดีที่สุดไม่ควรติดขัด

ด้วยเหตุนี้เราจึงใช้หลายเธรดเพื่อดำเนินการแบบขนาน เมื่อเราได้รับการตอบสนองจากเซิร์ฟเวอร์เราจำเป็นต้องอัพเดต UI เราต้องแจ้งจากเธรดนั้นให้อัปเดต จากโลกที่ใช้งานได้ การเรียกใช้ฟังก์ชันประเภทนี้เรียกว่าวิธีการโทรกลับ เมื่อคุณเรียกใช้เมธอดเหล่านี้ส่วนควบคุมควรกลับไปที่เธรดหลัก ตัวอย่างเช่นวิธีการโทรกลับเป็นบล็อกในวัตถุประสงค์ -C


ใช่สิ่งที่คุณพูดนั้นสอดคล้องกับความเข้าใจของฉันหลังจากอ่านหนังสือ
ลูคัสลี่

2
วิธีการโทรกลับไม่เหมือนมัลติเธรด วิธีการโทรกลับเป็นเพียงตัวชี้ฟังก์ชั่น (ผู้รับมอบสิทธิ์) เธรดเป็นเรื่องเกี่ยวกับการสลับบริบท / การใช้งาน CPU ความแตกต่างที่ง่ายขึ้นอย่างมากคือการทำเกลียวนั้นเกี่ยวกับการปรับแต่ง CPU ให้ดีที่สุดสำหรับ UX ในขณะที่การเรียกกลับนั้นเกี่ยวกับการสลับหน่วยความจำสำหรับ polymorphism เนื่องจากเธรดอาจเรียกใช้การเรียกกลับไม่ได้หมายความว่าเธรดและการเรียกกลับมีความคล้ายคลึงกัน เธรดยังเรียกใช้เมธอดสแตติกบนคลาสสแตติก แต่ชนิดเธรดและสแตติกไม่เหมือนกัน
rism
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.