ตามที่ฉันเข้าใจใน Scala ฟังก์ชันอาจถูกเรียกใช้เช่นกัน
- โดยค่าหรือ
- โดยชื่อ
ตัวอย่างเช่นจากการประกาศดังต่อไปนี้เรารู้หรือไม่ว่าฟังก์ชันจะถูกเรียกใช้อย่างไร?
ประกาศ:
def f (x:Int, y:Int) = x;
โทร
f (1,2)
f (23+55,5)
f (12+3, 44*11)
มีกฎอะไรบ้าง?
ตามที่ฉันเข้าใจใน Scala ฟังก์ชันอาจถูกเรียกใช้เช่นกัน
ตัวอย่างเช่นจากการประกาศดังต่อไปนี้เรารู้หรือไม่ว่าฟังก์ชันจะถูกเรียกใช้อย่างไร?
ประกาศ:
def f (x:Int, y:Int) = x;
โทร
f (1,2)
f (23+55,5)
f (12+3, 44*11)
มีกฎอะไรบ้าง?
คำตอบ:
ตัวอย่างที่คุณให้ใช้เพียงการโทรตามค่าดังนั้นฉันจะให้ตัวอย่างใหม่ที่เรียบง่ายขึ้นและแสดงความแตกต่าง
ก่อนอื่นสมมติว่าเรามีฟังก์ชั่นที่มีผลข้างเคียง Intฟังก์ชั่นนี้จะพิมพ์อะไรบางอย่างออกมาแล้วส่งกลับ
def something() = {
println("calling something")
1 // return value
}
ตอนนี้เรากำลังจะกำหนดฟังก์ชันสองฟังก์ชันที่ยอมรับIntอาร์กิวเมนต์ที่เหมือนกันทุกประการยกเว้นว่าจะรับอาร์กิวเมนต์ในลักษณะ call-by-value ( x: Int) และอีกฟังก์ชันหนึ่งในลักษณะ call-by-name ( 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
ดังนั้นคุณจะเห็นได้ว่าในเวอร์ชันการโทรตามค่าผลข้างเคียงของการเรียกฟังก์ชันที่ส่งผ่าน ( something()) เกิดขึ้นเพียงครั้งเดียวเท่านั้น อย่างไรก็ตามในเวอร์ชันการโทรตามชื่อผลข้างเคียงเกิดขึ้นสองครั้ง
นี่เป็นเพราะฟังก์ชั่นการโทรตามค่าคำนวณค่าของนิพจน์แบบพาส - อินก่อนเรียกใช้ฟังก์ชันดังนั้นจึงมีการเข้าถึงค่าเดียวกันทุกครั้ง ฟังก์ชั่นการโทรตามชื่อจะคำนวณค่าของนิพจน์ที่ส่งผ่านใหม่ทุกครั้งที่เข้าถึง
f(2)ถูกคอมไพล์เป็นนิพจน์ประเภทIntการเรียกใช้โค้ดที่สร้างขึ้นfพร้อมด้วยอาร์กิวเมนต์2และผลลัพธ์คือค่าของนิพจน์ หากข้อความเดียวกันนั้นถูกคอมไพล์เป็นนิพจน์ชนิด=> Intแล้วรหัสที่สร้างขึ้นจะใช้การอ้างอิงถึง "code block" บางประเภทเป็นค่าของนิพจน์ ทั้งสองวิธีค่าประเภทนั้นสามารถถูกส่งผ่านไปยังฟังก์ชันที่ต้องการพารามิเตอร์ประเภทนั้น ฉันค่อนข้างมั่นใจว่าคุณสามารถทำได้ด้วยการกำหนดตัวแปรโดยไม่มีพารามิเตอร์ส่งผ่าน ดังนั้นชื่อหรือการโทรมีอะไรเกี่ยวข้องกับมัน
=> Intเป็น "ฟังก์ชั่นที่ไม่มีข้อโต้แย้งที่สร้าง Int" มันแตกต่างกัน() => Intอย่างไร? Scala ดูเหมือนว่าจะปฏิบัติต่อสิ่งเหล่านี้แตกต่างกันไปตัวอย่างเช่น=> Intดูเหมือนจะไม่ทำงานเป็นประเภทของ a valเฉพาะกับประเภทของพารามิเตอร์
=> Intคือความสะดวกสบายและมันไม่ได้ถูกนำมาใช้อย่างถูกต้องตามวัตถุฟังก์ชั่น (สมมุติว่าทำไมคุณไม่สามารถมีตัวแปรประเภท=> Intแม้ว่าจะไม่มีเหตุผลพื้นฐานว่าทำไมสิ่งนี้ถึงใช้งานไม่ได้) () => Intเป็นฟังก์ชั่นที่ไม่มีข้อโต้แย้งอย่างชัดเจนว่าจะส่งคืนIntซึ่งจะต้องมีการเรียกอย่างชัดเจนและสามารถส่งผ่านเป็นฟังก์ชั่น => Intคือการจัดเรียงของ "ร็อกซี่Int" และสิ่งเดียวที่คุณสามารถทำอะไรกับมันเป็นเรียกว่า (โดยปริยาย) Intที่จะได้รับ
นี่คือตัวอย่างจาก Martin Odersky:
def test (x:Int, y: Int)= x*x
เราต้องการตรวจสอบกลยุทธ์การประเมินและกำหนดว่ากลยุทธ์ใดจะเร็วกว่า (น้อยกว่าขั้นตอน) ในเงื่อนไขเหล่านี้:
test (2,3)
เรียกค่า: ทดสอบ (2,3) -> 2 * 2 -> 4
เรียกตามชื่อ: ทดสอบ (2,3) -> 2 * 2 -> 4
ผลลัพธ์ที่ได้จะมาถึงด้วยจำนวนขั้นตอนเท่ากัน
test (3+4,8)
เรียกค่า: ทดสอบ (7,8) -> 7 * 7 -> 49
เรียกชื่อ: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
ที่นี่โทร โดยค่าจะเร็วขึ้น
test (7,2*4)
เรียกค่า: ทดสอบ (7,8) -> 7 * 7 -> 49
โทรหาโดยชื่อ: 7 * 7 -> 49 ที่
นี่เรียกตามชื่อเร็วกว่า
test (3+4, 2*4)
เรียกค่า: ทดสอบ (7,2 * 4) -> ทดสอบ (7, 8) -> 7 * 7 -> 49
ชื่อเรียก: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
ผลลัพธ์ถึงขั้นตอนเดียวกัน
def test (x:Int, y: => Int) = x * xหมายเหตุว่าพารามิเตอร์ y ไม่เคยถูกใช้
ในกรณีของตัวอย่างของคุณพารามิเตอร์ทั้งหมดจะได้รับการประเมินก่อนที่มันเรียกว่าในการทำงานในขณะที่คุณกำลังเพียงการกำหนดให้พวกเขาด้วยค่า หากคุณต้องการกำหนดพารามิเตอร์ตามชื่อคุณควรผ่านการบล็อกรหัส:
def f(x: => Int, y:Int) = x
วิธีนี้พารามิเตอร์xจะไม่ถูกประเมินจนกว่าจะถูกเรียกในฟังก์ชัน
โพสต์เล็ก ๆนี้ที่นี่อธิบายอย่างนี้เช่นกัน
ในการทำซ้ำจุดของเบ็นในความคิดเห็นข้างต้นฉันคิดว่าเป็นการดีที่สุดที่จะนึกถึง "call-by-name" เป็นเพียงน้ำตาลประโยค parser เพียงแค่ล้อมรอบนิพจน์ในฟังก์ชั่นที่ไม่ระบุชื่อเพื่อให้พวกเขาสามารถเรียกได้ในภายหลังเมื่อมีการใช้
มีผลบังคับใช้แทนการกำหนด
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
และทำงาน:
scala> callByName(something())
calling something
x1=1
calling something
x2=1
คุณสามารถเขียน:
def callAlsoByName(x: () => Int) = {
println("x1=" + x())
println("x2=" + x())
}
และเรียกใช้ดังต่อไปนี้สำหรับเอฟเฟกต์เดียวกัน:
callAlsoByName(() => {something()})
calling something
x1=1
calling something
x2=1
=> T () => Tฟังก์ชั่นที่ใช้ชนิดแรกเป็นพารามิเตอร์จะไม่ยอมรับที่สองสกาล่าเก็บข้อมูลเพียงพอในการเพิ่มความคิดเห็น@ScalaSignatureเพื่อให้เกิดข้อผิดพลาดในการคอมไพล์ครั้งนี้ bytecode สำหรับทั้งสอง=> Tและเป็นเหมือนกันและแม้ว่าเป็น() => T Function0ดูคำถามนี้สำหรับรายละเอียดเพิ่มเติม
ฉันจะพยายามอธิบายโดยกรณีการใช้งานที่ง่ายมากกว่าเพียงแค่ให้ตัวอย่าง
ลองนึกภาพคุณต้องการสร้าง"แอป nagger"ที่จะทำให้ Nag ทุกครั้งตั้งแต่ครั้งสุดท้ายที่คุณถูกจับ
ตรวจสอบการใช้งานต่อไปนี้:
object main {
def main(args: Array[String]) {
def onTime(time: Long) {
while(time != time) println("Time to Nag!")
println("no nags for you!")
}
def onRealtime(time: => Long) {
while(time != time) println("Realtime Nagging executed!")
}
onTime(System.nanoTime())
onRealtime(System.nanoTime())
}
}
ในการนำไปปฏิบัติข้างต้นผู้ที่มีเหตุมีผลจะใช้งานได้ก็ต่อเมื่อผ่านชื่อเหตุผลก็คือเมื่อผ่านตามตัวอักษรมันจะถูกนำมาใช้ใหม่และดังนั้นค่าจะไม่ถูกประเมินอีกครั้งในขณะที่เมื่อผ่านชื่อค่าจะได้รับการประเมินทุก เวลาที่เข้าถึงตัวแปร
โดยทั่วไปแล้วพารามิเตอร์ของฟังก์ชั่นคือพารามิเตอร์ต่อค่า นั่นคือค่าของพารามิเตอร์จะถูกกำหนดก่อนที่มันจะถูกส่งผ่านไปยังฟังก์ชั่น แต่ถ้าเราต้องเขียนฟังก์ชั่นที่ยอมรับว่าเป็นนิพจน์ที่เราไม่ต้องการให้ประเมินจนกว่ามันจะถูกเรียกใช้ในฟังก์ชั่นของเรา สำหรับกรณีนี้ Scala นำเสนอพารามิเตอร์แบบเรียกตามชื่อ
กลไก call-by-name ผ่านบล็อกรหัสไปยัง callee และทุกครั้งที่ callee เข้าถึงพารามิเตอร์บล็อกรหัสจะถูกดำเนินการและค่าจะถูกคำนวณ
object Test {
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)
t
}
}
1. C: /> scalac Test.scala 2. การทดสอบแบบสกาล่า 3. ในวิธีการล่าช้า 4. ใช้เวลาเป็นนาโนวินาที 5. พารามิเตอร์: 81303808765843 6. รับเวลาเป็นนาโนวินาที
ตามที่ฉันสันนิษฐานว่าcall-by-valueฟังก์ชั่นที่กล่าวถึงข้างต้นส่งผ่านค่าของฟังก์ชัน ตามที่Martin Oderskyมันเป็นกลยุทธ์การประเมินผลตามด้วยสกาล่าที่มีบทบาทสำคัญในการประเมินฟังก์ชั่น call-by-nameแต่ทำให้มันง่ายที่จะ Higher-Order-Functionsเช่นผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์กับวิธีการที่เรียกว่า เมื่อวิธีการเข้าถึงค่าของพารามิเตอร์ที่ส่งผ่านมันเรียกการดำเนินงานของฟังก์ชั่นที่ส่งผ่าน ดังต่อไปนี้:
ตามตัวอย่าง @dhg ให้สร้างวิธีแรกเป็น:
def something() = {
println("calling something")
1 // return value
}
ฟังก์ชันนี้มีหนึ่งprintlnข้อความสั่งและส่งคืนค่าจำนวนเต็ม สร้างฟังก์ชั่นที่มีข้อโต้แย้งเป็นcall-by-name:
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
พารามิเตอร์ฟังก์ชั่นนี้จะกำหนดฟังก์ชั่นที่ไม่ระบุชื่อที่ได้กลับมาเป็นจำนวนเต็มหนึ่งค่า ในนี้xมีความหมายของฟังก์ชั่นที่ได้0ผ่านการขัดแย้ง แต่intค่าตอบแทนและsomethingฟังก์ชั่นของเรามีลายเซ็นเดียวกัน callByNameเมื่อเราเรียกใช้ฟังก์ชันที่เราผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์ไป แต่ในกรณีของcall-by-valueมันเพียงส่งค่าจำนวนเต็มไปยังฟังก์ชัน เราเรียกฟังก์ชันดังต่อไปนี้:
scala> callByName(something())
calling something
x1=1
calling something
x2=1
ในที่นี้เราsomethingใช้วิธีการที่เรียกว่าสองครั้งเพราะเมื่อเราเข้าถึงค่าของxในcallByNameวิธีการโทรไปยัง defintion ของsomethingวิธีการ
การโทรตามค่าเป็นกรณีการใช้งานทั่วไปตามที่อธิบายโดยคำตอบมากมายที่นี่ ..
Call-by-nameจะส่งรหัสบล็อกไปยังผู้โทรและทุกครั้งที่ผู้เรียกเข้าถึงพารามิเตอร์บล็อกโค้ดจะถูกดำเนินการและค่าจะถูกคำนวณ
ฉันจะพยายามสาธิตการโทรด้วยชื่อแบบง่าย ๆ ด้วยกรณีการใช้ด้านล่าง
ตัวอย่างที่ 1:
ตัวอย่าง / กรณีใช้งานอย่างง่ายของการโทรตามชื่ออยู่ด้านล่างฟังก์ชั่นซึ่งรับฟังก์ชั่นเป็นพารามิเตอร์และให้เวลาผ่านไป
/**
* Executes some code block and prints to stdout the
time taken to execute the block
for interactive testing and debugging.
*/
def time[T](f: => T): T = {
val start = System.nanoTime()
val ret = f
val end = System.nanoTime()
println(s"Time taken: ${(end - start) / 1000 / 1000} ms")
ret
}
ตัวอย่างที่ 2:
apache spark (with scala) ใช้การบันทึกโดยใช้การโทรตามชื่อดูLoggingลักษณะ
ที่มันประเมินว่าขี้เกียจlog.isInfoEnabledหรือไม่จากวิธีการด้านล่าง
protected def logInfo(msg: => String) {
if (log.isInfoEnabled) log.info(msg)
}
ในCall by Valueค่าของนิพจน์จะถูกคำนวณล่วงหน้าในเวลาที่เรียกใช้ฟังก์ชันและค่าเฉพาะนั้นจะถูกส่งผ่านเป็นพารามิเตอร์ไปยังฟังก์ชันที่เกี่ยวข้อง ค่าเดียวกันนี้จะถูกใช้ตลอดการทำงาน
ในขณะที่Call by Nameนิพจน์จะถูกส่งเป็นพารามิเตอร์ไปยังฟังก์ชันและคำนวณเฉพาะภายในฟังก์ชันเมื่อใดก็ตามที่พารามิเตอร์นั้นเรียกว่า
ความแตกต่างระหว่าง Call by Name และ Call by Value ใน Scala สามารถเข้าใจได้ดีขึ้นด้วยตัวอย่างด้านล่าง:
ตัวอย่างโค้ด
object CallbyExample extends App {
// function definition of call by value
def CallbyValue(x: Long): Unit = {
println("The current system time via CBV: " + x);
println("The current system time via CBV " + x);
}
// function definition of call by name
def CallbyName(x: => Long): Unit = {
println("The current system time via CBN: " + x);
println("The current system time via CBN: " + x);
}
// function call
CallbyValue(System.nanoTime());
println("\n")
CallbyName(System.nanoTime());
}
เอาท์พุต
The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521
The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589
ในตัวอย่างโค้ดข้างต้นสำหรับการเรียกใช้ฟังก์ชันCallbyValue (System.nanoTime ())เวลา nano ของระบบจะถูกคำนวณล่วงหน้าและค่าที่คำนวณล่วงหน้าได้ถูกส่งผ่านพารามิเตอร์ไปยังการเรียกใช้ฟังก์ชัน
แต่ในการเรียกใช้ฟังก์ชัน CallbyName (System.nanoTime ())นิพจน์ "System.nanoTime ())" จะถูกส่งผ่านเป็นพารามิเตอร์สำหรับการเรียกฟังก์ชันและค่าของนิพจน์นั้นจะถูกคำนวณเมื่อมีการใช้พารามิเตอร์ภายในฟังก์ชันนั้น .
สังเกตุนิยามฟังก์ชั่นของฟังก์ชัน CallbyName โดยที่มีสัญลักษณ์=>คั่นพารามิเตอร์xและประเภทข้อมูล สัญลักษณ์เฉพาะนั้นมีบ่งชี้ว่าฟังก์ชั่นการโทรตามประเภทชื่อ
กล่าวอีกนัยหนึ่งอาร์กิวเมนต์การเรียกใช้ตามค่าฟังก์ชันจะได้รับการประเมินหนึ่งครั้งก่อนที่จะเข้าสู่ฟังก์ชัน แต่การเรียกใช้โดยอาร์กิวเมนต์ของฟังก์ชันชื่อจะได้รับการประเมินภายในฟังก์ชันเมื่อจำเป็นเท่านั้น
หวังว่านี่จะช่วยได้!
นี่คือตัวอย่างด่วนที่ฉันเขียนขึ้นเพื่อช่วยเพื่อนร่วมงานของฉันที่กำลังเรียนหลักสูตรสกาล่า สิ่งที่ฉันคิดว่าน่าสนใจคือมาร์ตินไม่ได้ใช้คำตอบ && คำถามที่นำเสนอก่อนหน้านี้ในการบรรยายเป็นตัวอย่าง ไม่ว่าในกรณีใด ๆ ฉันหวังว่าสิ่งนี้จะช่วยได้
val start = Instant.now().toEpochMilli
val calc = (x: Boolean) => {
Thread.sleep(3000)
x
}
def callByValue(x: Boolean, y: Boolean): Boolean = {
if (!x) x else y
}
def callByName(x: Boolean, y: => Boolean): Boolean = {
if (!x) x else y
}
new Thread(() => {
println("========================")
println("Call by Value " + callByValue(false, calc(true)))
println("Time " + (Instant.now().toEpochMilli - start) + "ms")
println("========================")
}).start()
new Thread(() => {
println("========================")
println("Call by Name " + callByName(false, calc(true)))
println("Time " + (Instant.now().toEpochMilli - start) + "ms")
println("========================")
}).start()
Thread.sleep(5000)
ผลลัพธ์ของรหัสจะเป็นดังต่อไปนี้:
========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================
พารามิเตอร์มักจะผ่านค่าซึ่งหมายความว่าพวกเขาจะได้รับการประเมินก่อนที่จะถูกแทนที่ในร่างกายของฟังก์ชั่น
คุณสามารถบังคับให้พารามิเตอร์เรียกตามชื่อโดยใช้ลูกศรคู่เมื่อกำหนดฟังก์ชัน
// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1
// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)
// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))
// will not terminate, since loop(2) will evaluate.
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ...
มีคำตอบที่น่าอัศจรรย์มากมายสำหรับคำถามนี้ในอินเทอร์เน็ต ฉันจะเขียนคำอธิบายและตัวอย่างมากมายที่รวบรวมไว้เกี่ยวกับหัวข้อในกรณีที่บางคนอาจพบว่ามีประโยชน์
บทนำ
เรียกค่าตาม (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
}
การดูตัวอย่างจะช่วยให้คุณเข้าใจความแตกต่างได้ดีขึ้น
มาเรียนฟังก์ชั่นง่ายๆที่คืนค่าเวลาปัจจุบัน:
def getTime = System.currentTimeMillis
ตอนนี้เราจะกำหนดฟังก์ชั่นตามชื่อที่พิมพ์ล่าช้าสองครั้งภายในหนึ่งวินาที:
def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}
และหนึ่งค่า :
def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}
ตอนนี้ให้โทรหากัน:
getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325
getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846
CallByNameถูกเรียกใช้เมื่อถูกใช้และcallByValueถูกเรียกใช้เมื่อใดก็ตามที่พบคำสั่ง
ตัวอย่างเช่น:-
ฉันมีวงวนไม่สิ้นสุดคือถ้าคุณใช้งานฟังก์ชั่นนี้เราจะไม่ได้รับscalaพรอมต์
scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int
callByNameฟังก์ชั่นใช้เวลาดังกล่าวข้างต้นloopวิธีการเป็นอาร์กิวเมนต์และมันก็ไม่เคยใช้ภายในร่างกายของตน
scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int
ในการดำเนินการของcallByNameวิธีการที่เราไม่พบปัญหาใด ๆ (เราได้รับscalaพรอมต์กลับ) เนื่องจากเราไม่มีที่ใช้ฟังก์ชั่นวงภายในcallByNameฟังก์ชั่น
scala> callByName(1,loop(10))
res1: Int = 1
scala>
callByValueฟังก์ชั่นใช้เวลาดังกล่าวข้างต้นloopวิธีการเป็นพารามิเตอร์เป็นผลภายในฟังก์ชั่นหรือการแสดงออกที่ถูกประเมินก่อนที่จะดำเนินฟังก์ชั่นด้านนอกโดยมีloopฟังก์ชั่นการดำเนินการซ้ำและเราไม่เคยได้รับscalaกลับพร้อมท์
scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int
scala> callByValue(1,loop(1))
ดูนี่:
object NameVsVal extends App {
def mul(x: Int, y: => Int) : Int = {
println("mul")
x * y
}
def add(x: Int, y: Int): Int = {
println("add")
x + y
}
println(mul(3, add(2, 1)))
}
y: => Int คือการโทรตามชื่อ สิ่งที่ถูกส่งเป็นชื่อเรียกจะเพิ่ม (2, 1) สิ่งนี้จะถูกประเมินอย่างขี้เกียจ ดังนั้นเอาต์พุตบนคอนโซลจะเป็น "mul" ตามด้วย "เพิ่ม" แม้ว่าการเพิ่มดูเหมือนจะถูกเรียกก่อน การโทรตามชื่อทำหน้าที่เหมือนกับการส่งต่อตัวชี้ฟังก์ชัน
ตอนนี้เปลี่ยนจาก y: => Int เป็น y: Int คอนโซลจะแสดง "เพิ่ม" ตามด้วย "mul"! วิธีการประเมินตามปกติ
ฉันไม่คิดว่าคำตอบทั้งหมดที่นี่ให้เหตุผลที่ถูกต้อง:
การเรียกตามค่าอาร์กิวเมนต์จะถูกคำนวณเพียงครั้งเดียว:
def f(x : Int, y :Int) = x
// following the substitution model
f(12 + 3, 4 * 11)
f(15, 4194304)
15
คุณสามารถเห็นข้างต้นว่าข้อโต้แย้งทั้งหมดจะได้รับการประเมินว่าไม่จำเป็นโดยปกติแล้วcall-by-valueจะเร็ว แต่ไม่เหมือนในกรณีนี้
หากกลยุทธ์การประเมินผลเป็นเช่นcall-by-nameนั้นการสลายตัวจะเป็นดังนี้:
f(12 + 3, 4 * 11)
12 + 3
15
ดังที่คุณเห็นข้างต้นเราไม่จำเป็นต้องประเมิน4 * 11และบันทึกการคำนวณเล็กน้อยซึ่งอาจเป็นประโยชน์ในบางครั้ง
=> Intเป็นประเภทที่แตกต่างจากInt; มันเป็น "ฟังก์ชั่นของการขัดแย้งไม่มีที่จะสร้างInt" VSIntเพียง เมื่อคุณมีฟังก์ชั่นชั้นหนึ่งคุณไม่จำเป็นต้องประดิษฐ์คำศัพท์การโทรตามชื่อเพื่ออธิบายสิ่งนี้