Gọi theo tên và gọi theo giá trị trong Scala, cần làm rõ


239

Theo tôi hiểu, trong Scala, một hàm có thể được gọi là

  • theo giá trị hoặc
  • bằng tên

Ví dụ, đưa ra các khai báo sau, chúng ta có biết hàm sẽ được gọi như thế nào không?

Tờ khai:

def  f (x:Int, y:Int) = x;

Gọi

f (1,2)
f (23+55,5)
f (12+3, 44*11)

Các quy tắc xin vui lòng là gì?

Câu trả lời:


539

Ví dụ bạn đã đưa ra chỉ sử dụng gọi theo giá trị, vì vậy tôi sẽ đưa ra một ví dụ mới, đơn giản hơn, cho thấy sự khác biệt.

Đầu tiên, giả sử chúng ta có một chức năng với tác dụng phụ. Hàm này in một cái gì đó ra và sau đó trả về một Int.

def something() = {
  println("calling something")
  1 // return value
}

Bây giờ chúng ta sẽ định nghĩa hai hàm chấp nhận các Intđối số hoàn toàn giống nhau ngoại trừ một hàm lấy đối số theo kiểu gọi theo giá trị ( x: Int) và hàm kia theo kiểu gọi theo tên ( x: => Int).

def callByValue(x: Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

Bây giờ điều gì xảy ra khi chúng ta gọi chúng với chức năng tác dụng phụ?

scala> callByValue(something())
calling something
x1=1
x2=1

scala> callByName(something())
calling something
x1=1
calling something
x2=1

Vì vậy, bạn có thể thấy rằng trong phiên bản gọi theo giá trị, tác dụng phụ của lệnh gọi hàm truyền vào ( something()) chỉ xảy ra một lần. Tuy nhiên, trong phiên bản gọi bằng tên, hiệu ứng phụ đã xảy ra hai lần.

Điều này là do các hàm gọi theo giá trị tính toán giá trị của biểu thức được truyền trước khi gọi hàm, do đó, cùng một giá trị được truy cập mỗi lần. Thay vào đó, các hàm gọi theo tên sẽ tính toán lại giá trị của biểu thức truyền vào mỗi khi nó được truy cập.


296
Tôi đã luôn nghĩ thuật ngữ này là không cần thiết gây nhầm lẫn. Một hàm có thể có nhiều tham số khác nhau về trạng thái gọi theo tên và trạng thái gọi theo giá trị của chúng. Vì vậy, không phải là một hàm là gọi theo tên hoặc gọi theo giá trị, mà mỗi tham số của nó có thể là tên- pass -by-name hoặc pass-by-value. Hơn nữa, "gọi theo tên" không liên quan gì đến tên . => Intlà một loại khác với Int; đó là "chức năng không có đối số sẽ tạo ra Int" so với chỉ Int. Khi bạn đã có các hàm hạng nhất, bạn không cần phải phát minh thuật ngữ gọi theo tên để mô tả điều này.
Bến

2
@Ben, điều đó giúp trả lời một vài câu hỏi, cảm ơn. Tôi muốn nhiều bài viết giải thích ngữ nghĩa của việc vượt qua tên này rõ ràng.
Christopher Poile 18/03/13

3
@S006Ober Nếu văn bản f(2)được biên dịch dưới dạng biểu thức Int, mã được tạo sẽ gọi fvới đối số 2và kết quả là giá trị của biểu thức. Nếu cùng một văn bản được biên dịch dưới dạng một biểu thức kiểu => Intthì mã được tạo sẽ sử dụng một tham chiếu đến một loại "khối mã" nào đó làm giá trị của biểu thức. Dù bằng cách nào, một giá trị của loại đó có thể được truyền cho một hàm mong đợi một tham số của loại đó. Tôi khá chắc chắn rằng bạn có thể làm điều này với sự phân công biến, không có tham số nào xuất hiện trong tầm nhìn. Vì vậy, tên hoặc gọi có liên quan gì với nó?
Ben

4
@Ben Vậy nếu => Int"hàm không có đối số tạo Int" thì nó khác với () => Intnhư thế nào? Scala dường như đối xử với những điều này khác nhau, ví dụ => Intrõ ràng không hoạt động như kiểu a val, chỉ là kiểu của một tham số.
Tim Goodman

5
@Timoodman Bạn nói đúng, nó phức tạp hơn một chút so với tôi đã làm. => Intlà một sự tiện lợi và nó không được triển khai chính xác như một đối tượng hàm (có lẽ là lý do tại sao bạn không thể có các biến loại => Int, mặc dù không có lý do cơ bản nào khiến nó không hoạt động). () => Intrõ ràng một chức năng của không có đối số đó sẽ trở lại một Int, mà cần phải được gọi một cách rõ ràng và có thể được thông qua như là một hàm. => Intlà một loại "proxy Int" và điều duy nhất bạn có thể làm với nó là gọi nó (ngầm) để lấy Int.
Ben

51

Dưới đây là một ví dụ từ Martin Oderky:

def test (x:Int, y: Int)= x*x

Chúng tôi muốn kiểm tra chiến lược đánh giá và xác định cái nào nhanh hơn (ít bước hơn) trong các điều kiện sau:

test (2,3)

gọi theo giá trị: test (2,3) -> 2 * 2 -> 4
gọi theo tên: test (2,3) -> 2 * 2 -> 4
Ở đây kết quả đạt được với cùng số bước.

test (3+4,8)

gọi theo giá trị: test (7,8) -> 7 * 7 -> 49
gọi theo tên: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Ở đây gọi bởi giá trị nhanh hơn.

test (7,2*4)

gọi theo giá trị: test (7,8) -> 7 * 7 -> 49
gọi theo tên: 7 * 7 -> 49
Ở đây gọi theo tên nhanh hơn

test (3+4, 2*4) 

gọi theo giá trị: test (7,2 * 4) -> test (7, 8) -> 7 * 7 -> 49
gọi theo tên: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Kết quả đạt được trong cùng các bước.


1
Trong ví dụ thứ ba cho CBV, tôi nghĩ bạn có nghĩa là thử nghiệm (7,8) thay vì thử nghiệm (7,14)
Talonx

1
Ví dụ được lấy từ Coursera, nguyên tắc trong lập trình scala. Bài giảng 1.2. Cuộc gọi theo tên nên đọc def test (x:Int, y: => Int) = x * xlưu ý rằng tham số y không bao giờ được sử dụng.
dr jerry

1
Ví dụ tốt! Lấy từ Coursera MOOC :)
alxsimo

Đây là một lời giải thích tốt về sự khác biệt, nhưng không giải quyết được câu hỏi đang được hỏi, cụ thể là hai trong số đó là cuộc gọi Scala
db1234

16

Trong trường hợp ví dụ của bạn, tất cả các tham số sẽ được đánh giá trước khi nó được gọi trong hàm, vì bạn chỉ xác định chúng theo giá trị . Nếu bạn muốn xác định tham số của mình theo tên, bạn nên vượt qua một khối mã:

def f(x: => Int, y:Int) = x

Bằng cách này, tham số xsẽ không được đánh giá cho đến khi nó được gọi trong hàm.

Bài viết nhỏ ở đây giải thích điều này độc đáo quá.


10

Để lặp lại quan điểm của @ Ben trong các ý kiến ​​trên, tôi nghĩ tốt nhất nên nghĩ "gọi theo tên" chỉ là đường cú pháp. Trình phân tích cú pháp chỉ bao bọc các biểu thức trong các hàm ẩn danh, để chúng có thể được gọi tại một điểm sau đó, khi chúng được sử dụng.

Trong thực tế, thay vì xác định

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

và chạy:

scala> callByName(something())
calling something
x1=1
calling something
x2=1

Bạn cũng có thể viết:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

Và chạy nó như sau cho cùng một hiệu ứng:

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

Tôi nghĩ bạn có nghĩa là: <! - ngôn ngữ: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))} và sau đó: <! - ngôn ngữ: lang-js -> callAlsoByName (() => Something ()) Tôi không nghĩ bạn cần các dấu ngoặc nhọn xung quanh một cái gì đó () trong cuộc gọi cuối cùng này. Lưu ý: Tôi đã cố gắng chỉ chỉnh sửa câu trả lời của bạn nhưng bản chỉnh sửa của tôi đã bị từ chối bởi những người đánh giá nói rằng đó phải là một nhận xét hoặc câu trả lời riêng thay thế.
lambdista

Rõ ràng bạn không thể sử dụng tô sáng cú pháp trong các bình luận, vì vậy hãy bỏ qua phần "<! - ngôn ngữ: lang-scala ->"! Tôi đã có thể chỉnh sửa nhận xét của riêng mình nhưng bạn chỉ được phép thực hiện trong vòng 5 phút! :)
lambdista

1
Gần đây tôi cũng chạy vào đây. Về mặt khái niệm có thể nghĩ rằng nó như thế này nhưng scala phân biệt giữa => T() => T. Một hàm lấy loại thứ nhất làm tham số, sẽ không chấp nhận loại thứ hai, scala lưu trữ đủ thông tin trong @ScalaSignaturechú thích để đưa ra lỗi thời gian biên dịch cho điều này. Mã byte cho cả hai => T() => Tgiống nhau và là một Function0. Xem câu hỏi này để biết thêm chi tiết.
vsnyc

6

Tôi sẽ cố gắng giải thích bằng một trường hợp sử dụng đơn giản thay vì chỉ cung cấp một ví dụ

Hãy tưởng tượng bạn muốn xây dựng một "ứng dụng nagger" sẽ giúp bạn Nag mỗi lần kể từ lần cuối bạn bị cằn nhằn.

Kiểm tra các triển khai sau:

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

Trong cách thực hiện ở trên, nagger sẽ chỉ hoạt động khi truyền theo tên, lý do là, khi truyền theo giá trị, nó sẽ được sử dụng lại và do đó giá trị sẽ không được đánh giá lại trong khi khi chuyển qua tên, giá trị sẽ được đánh giá lại mỗi thời gian các biến được truy cập


4

Thông thường, các tham số cho các chức năng là các tham số theo giá trị; nghĩa là, giá trị của tham số được xác định trước khi nó được truyền cho hàm. Nhưng điều gì sẽ xảy ra nếu chúng ta cần viết một hàm chấp nhận như một tham số một biểu thức mà chúng ta không muốn đánh giá cho đến khi nó được gọi trong hàm của chúng ta? Đối với trường hợp này, Scala cung cấp các tham số gọi theo tên.

Một cơ chế gọi bằng tên sẽ chuyển một khối mã đến callee và mỗi lần callee truy cập tham số, khối mã được thực thi và giá trị được tính.

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. Kiểm tra Scala
 3. Trong phương pháp trì hoãn
 4. Nhận thời gian tính bằng nano giây
 5. Tham số: 81303808765843
 6. Nhận thời gian tính bằng nano giây

2

Như tôi giả sử, call-by-valuehàm như đã thảo luận ở trên chỉ chuyển các giá trị cho hàm. Theo Martin OderskyĐó là một chiến lược Đánh giá theo sau bởi một Scala đóng vai trò quan trọng trong đánh giá chức năng. Nhưng, làm cho nó đơn giản để call-by-name. Nó giống như một hàm truyền làm đối số cho phương thức cũng biết là Higher-Order-Functions. Khi phương thức truy cập giá trị của tham số đã truyền, nó gọi việc thực hiện các hàm được truyền. như sau:

Theo ví dụ @dhg, tạo phương thức đầu tiên là:

def something() = {
 println("calling something")
 1 // return value
}  

Hàm này chứa một printlncâu lệnh và trả về một giá trị nguyên. Tạo hàm, người có đối số là call-by-name:

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

Tham số hàm này, được định nghĩa một hàm ẩn danh đã trả về một giá trị nguyên. Trong phần này xchứa một định nghĩa về hàm đã 0truyền đối số nhưng trả về intgiá trị và somethinghàm của chúng ta chứa cùng chữ ký. Khi chúng ta gọi hàm, chúng ta truyền hàm dưới dạng đối số callByName. Nhưng trong trường hợp của call-by-valuenó chỉ truyền giá trị nguyên cho hàm. Chúng tôi gọi chức năng như sau:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

Trong chúng ta này somethingphương pháp gọi là hai lần, bởi vì khi chúng ta truy cập giá trị của xtrong callByNamephương pháp, gọi nó đến defintion của somethingphương pháp.


2

Gọi theo giá trị là trường hợp sử dụng chung như được giải thích bằng nhiều câu trả lời ở đây ..

Call-by-name chuyển một khối mã cho người gọi và mỗi lần người gọi truy cập tham số, khối mã được thực thi và giá trị được tính.

Tôi sẽ cố gắng thể hiện cuộc gọi theo tên đơn giản hơn với các trường hợp sử dụng bên dưới

Ví dụ 1:

Ví dụ đơn giản / trường hợp sử dụng cuộc gọi theo tên nằm dưới hàm, hàm này lấy chức năng làm tham số và cho thời gian trôi qua.

 /**
   * 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
  }

Ví dụ 2:

apache spark (với scala) sử dụng ghi nhật ký bằng cách sử dụng cuộc gọi theo tên, xem Loggingđặc điểm trong đó nó lười biếng đánh giá xem có log.isInfoEnabledhay không từ phương thức dưới đây.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

2

Trong một cuộc gọi theo giá trị , giá trị của biểu thức được tính toán trước tại thời điểm gọi hàm và giá trị cụ thể đó được truyền dưới dạng tham số cho hàm tương ứng. Giá trị tương tự sẽ được sử dụng tất cả trong suốt chức năng.

Trong khi trong Call by Name , chính biểu thức được truyền dưới dạng tham số cho hàm và nó chỉ được tính bên trong hàm, bất cứ khi nào tham số cụ thể đó được gọi.

Sự khác biệt giữa Gọi theo Tên và Gọi theo Giá trị trong Scala có thể được hiểu rõ hơn với ví dụ dưới đây:

Đoạn mã

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

Đầu ra

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

Trong đoạn mã trên, đối với hàm gọi CallbyValue (System.nanoTime ()) , thời gian nano của hệ thống được tính toán trước và giá trị được tính toán trước đó đã được chuyển một tham số cho lệnh gọi hàm.

Nhưng trong lệnh gọi hàm CallbyName (System.nanoTime ()) , chính biểu thức "System.nanoTime ())" được truyền dưới dạng tham số cho lệnh gọi hàm và giá trị của biểu thức đó được tính khi tham số đó được sử dụng bên trong hàm .

Lưu ý định nghĩa hàm của hàm CallbyName, trong đó có ký hiệu => phân tách tham số x và kiểu dữ liệu của nó. Biểu tượng cụ thể đó cho biết chức năng của cuộc gọi theo loại tên.

Nói cách khác, các đối số của hàm gọi theo giá trị được đánh giá một lần trước khi vào hàm, nhưng các đối số hàm gọi theo tên chỉ được đánh giá bên trong hàm khi chúng cần thiết.

Hi vọng điêu nay co ich!


2

Dưới đây là một ví dụ nhanh mà tôi đã mã hóa để giúp một đồng nghiệp của tôi hiện đang tham gia khóa học Scala. Điều tôi nghĩ là thú vị là Martin đã không sử dụng câu trả lời && câu hỏi được trình bày trước đó trong bài giảng làm ví dụ. Trong mọi trường hợp tôi hy vọng điều này sẽ giúp.

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)

Đầu ra của mã sẽ như sau:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================

1

Các tham số thường được truyền theo giá trị, có nghĩa là chúng sẽ được đánh giá trước khi được thay thế trong thân hàm.

Bạn có thể buộc một tham số được gọi theo tên bằng cách sử dụng mũi tên kép khi xác định hàm.

// 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) -> ... 

1

Đã có rất nhiều câu trả lời tuyệt vời cho câu hỏi này trên Internet. Tôi sẽ viết một bản tổng hợp một số giải thích và ví dụ tôi đã thu thập về chủ đề này, chỉ trong trường hợp ai đó có thể thấy nó hữu ích

GIỚI THIỆU

gọi theo giá trị (CBV)

Thông thường, tham số cho các chức năng là tham số gọi theo giá trị; nghĩa là, các tham số được ước tính từ trái sang phải để xác định giá trị của chúng trước khi chính hàm được ước tính

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

gọi bằng tên (CBN)

Nhưng điều gì sẽ xảy ra nếu chúng ta cần viết một hàm chấp nhận như một tham số một biểu thức mà chúng ta không đánh giá cho đến khi nó được gọi trong hàm của chúng ta? Đối với trường hợp này, Scala cung cấp các tham số gọi theo tên. Có nghĩa là tham số được truyền vào hàm như hiện tại và việc định giá của nó diễn ra sau khi thay thế

def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7

Một cơ chế gọi bằng tên chuyển một khối mã cho cuộc gọi và mỗi lần cuộc gọi truy cập tham số, khối mã được thực thi và giá trị được tính. Trong ví dụ sau, trì hoãn in một thông báo chứng minh rằng phương thức đã được nhập. Tiếp theo, trì hoãn in một tin nhắn với giá trị của nó. Cuối cùng, trả lại chậm '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)
       }
    }

Trong phương pháp trì hoãn
Nhận thời gian tính bằng nano giây Thông
số: 2027245119786400

PROS VÀ TIÊU DÙNG CHO MACHI TRƯỜNG HỢP

CBN: + Chấm dứt thường xuyên hơn * kiểm tra bên dưới chấm dứt * + Có lợi thế là không đánh giá được đối số hàm nếu tham số tương ứng không được sử dụng trong đánh giá thân hàm - Nó chậm hơn, nó tạo ra nhiều lớp hơn (nghĩa là chương trình mất lâu hơn để tải) và nó tiêu thụ nhiều bộ nhớ hơn.

CBV: + Nó thường hiệu quả hơn theo cấp số nhân so với CBN, vì nó tránh sự tính toán lặp lại này của các biểu thức đối số gọi bằng tên. Nó đánh giá mọi đối số chức năng chỉ một lần + Nó chơi đẹp hơn nhiều với các hiệu ứng bắt buộc và tác dụng phụ, bởi vì bạn có xu hướng biết rõ hơn khi các biểu thức sẽ được đánh giá. -Có thể dẫn đến một vòng lặp trong quá trình đánh giá tham số của nó * kiểm tra bên dưới chấm dứt *

Nếu chấm dứt không được đảm bảo thì sao?

-Nếu đánh giá CBV của một biểu thức e chấm dứt, thì đánh giá CBN của e cũng chấm dứt -Điều khác là không đúng

Ví dụ không chấm dứt

def first(x:Int, y:Int)=x

Hãy xem xét biểu thức đầu tiên (1, vòng lặp)

CBN: đầu tiên (1, vòng lặp) → 1 CBV: đầu tiên (1, vòng lặp) → giảm các đối số của biểu thức này. Vì một là một vòng lặp, nó làm giảm các đối số một cách vô tận. Nó không chấm dứt

NHỮNG KHÁC BIỆT TRONG MACHI TRƯỜNG HỢP

Hãy xác định một thử nghiệm phương pháp sẽ là

Def test(x:Int, y:Int) = x * x  //for call-by-value
Def test(x: => Int, y: => Int) = x * x  //for call-by-name

Kiểm tra Case1 (2,3)

test(2,3)2*24

Vì chúng tôi bắt đầu với các đối số đã được đánh giá, nó sẽ có cùng số lượng bước cho cuộc gọi theo giá trị và cuộc gọi theo tên

Thử nghiệm Case2 (3 + 4,8)

call-by-value: test(3+4,8) → test(7,8)7 * 749
call-by-name: (3+4)*(3+4)7 * (3+4)7 * 749

Trong trường hợp này, gọi theo giá trị thực hiện ít bước hơn

Thử nghiệm Case3 (7, 2 * 4)

call-by-value: test(7, 2*4) → test(7,8)7 * 749
call-by-name: (7)*(7)49

Chúng tôi tránh tính toán không cần thiết của đối số thứ hai

Kiểm tra Case4 (3 + 4, 2 * 4)

call-by-value: test(7, 2*4) → test(7,8)7 * 749
call-by-name: (3+4)*(3+4)7*(3+4)7*749

Phương pháp khác nhau

Đầu tiên, giả sử chúng ta có một chức năng với tác dụng phụ. Hàm này in ra một cái gì đó và sau đó trả về một Int.

def something() = {
  println("calling something")
  1 // return value
}

Bây giờ chúng ta sẽ định nghĩa hai hàm chấp nhận các đối số Int hoàn toàn giống nhau ngoại trừ một hàm lấy đối số theo kiểu gọi theo giá trị (x: Int) và hàm kia theo kiểu gọi theo tên (x: => Int).

def callByValue(x: Int) = {
  println("x1=" + x)
  println("x2=" + x)
}
def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

Bây giờ điều gì xảy ra khi chúng ta gọi chúng với chức năng tác dụng phụ?

scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1

Vì vậy, bạn có thể thấy rằng trong phiên bản gọi theo giá trị, tác dụng phụ của lệnh gọi hàm truyền vào (một cái gì đó ()) chỉ xảy ra một lần. Tuy nhiên, trong phiên bản gọi bằng tên, hiệu ứng phụ đã xảy ra hai lần.

Điều này là do các hàm gọi theo giá trị tính toán giá trị của biểu thức được truyền trước khi gọi hàm, do đó, cùng một giá trị được truy cập mỗi lần. Tuy nhiên, các hàm gọi theo tên sẽ tính toán lại giá trị của biểu thức truyền vào mỗi khi nó được truy cập.

VÍ DỤ Ở ĐÂU LÀ TỐT HƠN ĐỂ SỬ DỤNG CUỘC GỌI

Từ: https://stackoverflow.com/a/19036068/1773841

Ví dụ hiệu suất đơn giản: đăng nhập.

Hãy tưởng tượng một giao diện như thế này:

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}

Và sau đó được sử dụng như thế này:

logger.info("Time spent on X: " + computeTimeSpent)

Nếu phương thức thông tin không làm gì cả (vì giả sử, mức ghi nhật ký được định cấu hình cao hơn mức đó), thì computeTimeSpent không bao giờ được gọi, tiết kiệm thời gian. Điều này xảy ra rất nhiều với loggers, trong đó người ta thường thấy thao tác chuỗi có thể tốn kém so với các tác vụ được ghi.

Ví dụ đúng: toán tử logic.

Bạn có thể đã thấy mã như thế này:

if (ref != null && ref.isSomething)

Hãy tưởng tượng bạn sẽ khai báo && phương thức như thế này:

trait Boolean {
  def &&(other: Boolean): Boolean
}

sau đó, bất cứ khi nào ref là null, bạn sẽ gặp lỗi vì isS Something sẽ được gọi bằng nullreference trước khi được chuyển đến &&. Vì lý do này, tuyên bố thực tế là:

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) this else other
}

1

Đi qua một ví dụ sẽ giúp bạn hiểu rõ hơn về sự khác biệt.

Hãy xác định một chức năng đơn giản trả về thời gian hiện tại:

def getTime = System.currentTimeMillis

Bây giờ chúng ta sẽ xác định một hàm, theo tên , in hai lần bị trễ bởi một giây:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

Và một theo giá trị :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

Bây giờ hãy gọi mỗi:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

Kết quả sẽ giải thích sự khác biệt. Đoạn mã có sẵn ở đây .


0

CallByNameđược gọi khi được sử dụng và callByValueđược gọi bất cứ khi nào câu lệnh gặp phải.

Ví dụ:-

Tôi có một vòng lặp vô hạn tức là nếu bạn thực hiện chức năng này, chúng tôi sẽ không bao giờ nhận được scaladấu nhắc.

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

một callByNamehàm lấy loopphương thức trên làm đối số và nó không bao giờ được sử dụng bên trong cơ thể của nó.

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

Khi thực hiện callByNamephương thức, chúng tôi không tìm thấy bất kỳ vấn đề nào (chúng tôi nhận được scalalời nhắc lại) vì chúng tôi không sử dụng hàm lặp bên trong callByNamehàm.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

một callByValuehàm lấy loopphương thức trên làm tham số do kết quả bên trong hàm hoặc biểu thức được đánh giá trước khi thực hiện hàm ngoài ở đó bởi loophàm được thực hiện đệ quy và chúng tôi không bao giờ nhận được scaladấu nhắc lại.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))

0

Xem cái này:

    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 được gọi theo tên. Những gì được truyền dưới dạng gọi theo tên là add (2, 1). Điều này sẽ được đánh giá một cách lười biếng. Vì vậy, đầu ra trên bàn điều khiển sẽ là "mul" theo sau là "add", mặc dù add dường như được gọi đầu tiên. Gọi theo tên hoạt động như một loại vượt qua một con trỏ hàm.
Bây giờ thay đổi từ y: => Int thành y: Int. Bảng điều khiển sẽ hiển thị "thêm" theo sau là "mul"! Cách đánh giá thông thường.


-2

Tôi không nghĩ rằng tất cả các câu trả lời ở đây đều có lý do chính đáng:

Trong cuộc gọi theo giá trị, các đối số được tính chỉ một lần:

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

bạn có thể thấy ở trên rằng tất cả các đối số được đánh giá cho dù không cần thiết, thông thường call-by-valuecó thể nhanh nhưng không phải lúc nào cũng như trong trường hợp này.

Nếu chiến lược đánh giá là call-by-namethì sự phân rã sẽ là:

f(12 + 3, 4 * 11)
12 + 3
15

như bạn có thể thấy ở trên, chúng tôi không bao giờ cần phải đánh giá 4 * 11và do đó đã tiết kiệm được một chút tính toán đôi khi có thể có lợi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.