มีคำตอบที่น่าอัศจรรย์มากมายสำหรับคำถามนี้ในอินเทอร์เน็ต ฉันจะเขียนคำอธิบายและตัวอย่างมากมายที่รวบรวมไว้เกี่ยวกับหัวข้อในกรณีที่บางคนอาจพบว่ามีประโยชน์
บทนำ
เรียกค่าตาม (CBV)
โดยทั่วไปแล้วพารามิเตอร์ของฟังก์ชั่นคือพารามิเตอร์ที่เรียกตามค่า นั่นคือพารามิเตอร์จะถูกประเมินจากซ้ายไปขวาเพื่อกำหนดค่าก่อนที่จะประเมินฟังก์ชันเอง
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
โทรตามชื่อ (CBN)
แต่ถ้าเราต้องเขียนฟังก์ชั่นที่ยอมรับว่าเป็นนิพจน์ที่เราไม่ต้องประเมินจนกว่ามันจะถูกเรียกใช้ในฟังก์ชั่นของเรา สำหรับกรณีนี้ Scala นำเสนอพารามิเตอร์แบบเรียกตามชื่อ ความหมายพารามิเตอร์ถูกส่งผ่านไปยังฟังก์ชันตามที่เป็นอยู่และการประเมินค่าจะเกิดขึ้นหลังจากการทดแทน
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
กลไกการโทรตามชื่อส่งต่อบล็อครหัสไปยังการโทรและทุกครั้งที่การเรียกเข้าถึงพารามิเตอร์บล็อกรหัสจะถูกดำเนินการและค่าจะถูกคำนวณ ในตัวอย่างต่อไปนี้ล่าช้าพิมพ์ข้อความที่แสดงให้เห็นว่าวิธีการที่ได้รับการป้อน ถัดไปล่าช้าพิมพ์ข้อความที่มีค่า ในที่สุดผลตอบแทนล่าช้า 't':
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
ในวิธีการที่ล่าช้า
รับเวลาเป็นวินาทีนาโน
พารามิเตอร์: 2027245119786400
ข้อดีและข้อเสียสำหรับแต่ละกรณี
CBN:
+ ยุติบ่อยยิ่งขึ้น * ตรวจสอบการเลิกจ้างด้านล่าง * + มีข้อได้เปรียบที่อาร์กิวเมนต์ของฟังก์ชั่นไม่ได้รับการประเมินถ้าพารามิเตอร์ที่เกี่ยวข้องไม่ได้ใช้ในการประเมินผลของฟังก์ชั่นของร่างกาย - ช้าลงมันสร้างคลาสเพิ่มเติม โหลดนานขึ้น) และใช้หน่วยความจำเพิ่มขึ้น
CBV:
+ มันมักจะมีประสิทธิภาพมากกว่าชี้แจงแทน CBN เพราะมันหลีกเลี่ยงการคำนวณซ้ำของการแสดงออกของข้อโต้แย้งที่เรียกชื่อตามรายละเอียดซ้ำ มันประเมินทุก ๆ อาร์กิวเมนต์ของฟังก์ชั่นเพียงครั้งเดียว + มันเล่นได้ดีกว่ามากด้วยเอฟเฟกต์คำสั่งและผลข้างเคียงเพราะคุณมักจะรู้ดีกว่ามากเมื่อการแสดงออกจะถูกประเมิน - อาจนำไปสู่การวนซ้ำระหว่างการประเมินค่าพารามิเตอร์ * ตรวจสอบการเลิกจ้างด้านล่าง *
จะทำอย่างไรถ้าการเลิกจ้างไม่รับประกัน
- หากการประเมิน CBV ของนิพจน์ e สิ้นสุดลงการประเมิน CBN ของ e จะสิ้นสุดลงด้วย - ทิศทางอื่นไม่เป็นความจริง
ตัวอย่างที่ไม่สิ้นสุด
def first(x:Int, y:Int)=x
พิจารณาการแสดงออกก่อน (1, วน)
CBN: แรก (1, ลูป) → 1 CBV: แรก (1, ลูป) →ลดอาร์กิวเมนต์ของนิพจน์นี้ เนื่องจากหนึ่งเป็นลูปจึงลดการขัดแย้งอย่างไม่สิ้นสุด มันไม่ยุติ
ความแตกต่างในพฤติกรรมของแต่ละกรณี
ลองกำหนดวิธีการทดสอบที่จะ
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
การทดสอบ Case1 (2,3)
test(2,3) → 2*2 → 4
เนื่องจากเราเริ่มต้นด้วยอาร์กิวเมนต์ที่ประเมินแล้วมันจะเป็นขั้นตอนจำนวนเท่ากันสำหรับการโทรตามค่าและการโทรตามชื่อ
การทดสอบ Case2 (3 + 4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
ในกรณีนี้การโทรตามค่าจะดำเนินการตามขั้นตอนน้อยลง
การทดสอบ Case3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
เราหลีกเลี่ยงการคำนวณอาร์กิวเมนต์ที่สองโดยไม่จำเป็น
การทดสอบ Case4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
วิธีการที่แตกต่างกัน
ก่อนอื่นสมมติว่าเรามีฟังก์ชั่นที่มีผลข้างเคียง ฟังก์ชั่นนี้จะพิมพ์สิ่งที่ต้องการแล้วส่งกลับค่า Int
def something() = {
println("calling something")
1 // return value
}
ตอนนี้เรากำลังจะกำหนดฟังก์ชั่นสองตัวที่รับอาร์กิวเมนต์ของ Int ที่เหมือนกันทุกประการยกเว้นฟังก์ชั่นนั้นจะรับอาร์กิวเมนต์ในรูปแบบการโทรตามค่า (x: Int) และอีกฟังก์ชันหนึ่งในรูปแบบการโทรตามชื่อ (x: => Int)
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
ตอนนี้เกิดอะไรขึ้นเมื่อเราเรียกพวกมันมาพร้อมกับฟังก์ชั่นลดผลกระทบ
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
ดังนั้นคุณจะเห็นได้ว่าในเวอร์ชันการโทรตามค่าผลข้างเคียงของการเรียกฟังก์ชันที่ส่งผ่าน (บางสิ่ง ()) เกิดขึ้นเพียงครั้งเดียว อย่างไรก็ตามในเวอร์ชันการโทรตามชื่อผลข้างเคียงเกิดขึ้นสองครั้ง
นี่เป็นเพราะฟังก์ชั่นการโทรตามค่าคำนวณค่าของนิพจน์แบบพาส - อินก่อนเรียกใช้ฟังก์ชันดังนั้นจึงมีการเข้าถึงค่าเดียวกันทุกครั้ง อย่างไรก็ตามฟังก์ชัน call-by-name จะคำนวณค่าของนิพจน์แบบพาส - อินใหม่ทุกครั้งที่เข้าถึง
ตัวอย่างที่ดีกว่าในการใช้งาน CALL-BY-NAME
จาก: https://stackoverflow.com/a/19036068/1773841
ตัวอย่างประสิทธิภาพการทำงานที่เรียบง่าย: การบันทึก
ลองจินตนาการถึงอินเทอร์เฟซแบบนี้:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
แล้วใช้แบบนี้:
logger.info("Time spent on X: " + computeTimeSpent)
หากวิธีการข้อมูลไม่ได้ทำอะไร (เพราะพูดว่าระดับการบันทึกถูกกำหนดค่าให้สูงกว่านั้น) จากนั้น computeTimeSpent จะไม่ถูกเรียกใช้ซึ่งจะช่วยประหยัดเวลา สิ่งนี้เกิดขึ้นมากมายกับตัวบันทึกซึ่งมักจะเห็นการจัดการสตริงซึ่งอาจมีราคาแพงเมื่อเทียบกับงานที่ถูกบันทึก
ตัวอย่างความถูกต้อง: ตัวดำเนินการเชิงตรรกะ
คุณอาจเห็นโค้ดเช่นนี้:
if (ref != null && ref.isSomething)
ลองจินตนาการว่าคุณจะประกาศ && วิธีเช่นนี้:
trait Boolean {
def &&(other: Boolean): Boolean
}
จากนั้นเมื่อใดก็ตามที่การอ้างอิงเป็นโมฆะคุณจะได้รับข้อผิดพลาดเนื่องจาก isSomething จะถูกเรียกในการอ้างอิงก่อนที่จะถูกส่งผ่านไปยัง && ด้วยเหตุผลนี้การประกาศจริงคือ:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
เป็นประเภทที่แตกต่างจากInt
; มันเป็น "ฟังก์ชั่นของการขัดแย้งไม่มีที่จะสร้างInt
" VSInt
เพียง เมื่อคุณมีฟังก์ชั่นชั้นหนึ่งคุณไม่จำเป็นต้องประดิษฐ์คำศัพท์การโทรตามชื่อเพื่ออธิบายสิ่งนี้