簡體   English   中英

何時使用按名稱調用和按值調用?

[英]When to use call-by-name and call-by-value?

我了解按名稱調用和按值調用的基本概念,並且我還研究了一些示例。 但是,我不太清楚何時使用按名稱調用。 與其他呼叫類型相比,按名稱呼叫將具有顯着優勢或性能提升的真實場景是什么? 在設計方法時選擇調用類型的正確思路應該是什么?

有很多地方可以通過名稱來獲得性能甚至是正確性。

簡單的性能示例:日志記錄。 想象一個這樣的界面:

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

然后像這樣使用:

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

如果info方法不執行任何操作(例如,日志級別配置為高於此級別),則永遠不會computeTimeSpent ,從而節省時間。 這種情況在記錄器中經常發生,人們經常看到字符串操作相對於正在記錄的任務而言可能是昂貴的。

正確性示例:邏輯運算符。

你可能見過這樣的代碼:

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

假設你聲明&&方法是這樣的:

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

然后,每當refnull時,您都會收到錯誤消息,因為isSomething將在傳遞給&&之前在null引用上調用。 因此,實際的聲明是:

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

所以人們實際上可能想知道何時使用按值調用。 事實上,在 Haskell 編程語言中,一切的工作方式都類似於按名稱調用的工作方式(類似,但不一樣)。

不使用 call-by-name 有很好的理由:它更慢,它創建更多的類(意味着程序需要更長的加載時間),它消耗更多的內存,而且它的不同之處足以讓許多人難以理解它。

按名稱調用意味着在訪問值時對其進行評估,而按值調用則首先對值進行評估,然后將其傳遞給方法。

要查看差異,請考慮這個示例(完全非函數式編程,只有副作用;))。 假設您想創建一個函數來測量某些操作需要多少時間。 您可以通過名稱調用來做到這一點:

def measure(action: => Unit) = {
    println("Starting to measure time")
    val startTime = System.nanoTime
    action
    val endTime = System.nanoTime
    println("Operation took "+(endTime-startTime)+" ns")
}

measure {
    println("Will now sleep a little")
    Thread.sleep(1000)
}

你會得到結果(YMMV):

Starting to measure time
Will now sleep a little
Operation took 1000167919 ns

但是,如果您僅將 measure 的簽名更改為measure measure(action: Unit)以使其使用按值傳遞,則結果將是:

Will now sleep a little
Starting to measure time
Operation took 1760 ns

如您所見,甚至在measure開始之前就評估了該action ,並且由於在調用該方法之前該操作已經運行,因此經過的時間也接近於 0。

在這里,按名稱傳遞允許實現方法的預期行為。 在某些情況下,它不會影響正確性,但會影響性能,例如在日志框架中,如果不使用結果,可能根本不需要評估復雜的表達式。

可能解釋的簡單方法是

值調用函數在調用函數之前計算傳入表達式的值,因此每次都訪問相同的值。 但是,按名稱調用函數每次訪問時都會重新計算傳入表達式的值。

我一直認為這個術語是不必要的混亂。 一個函數可以有多個參數,它們的名稱調用和值調用狀態各不相同。 所以不是一個函數是按名稱調用或按值調用,而是它的每個參數都可能是按名稱傳遞或按值傳遞。 此外,“按名稱呼叫”與名稱無關。 => Int 是與 Int 不同的類型; 它是“將生成 Int 的無參數函數”與僅 Int 相比。 一旦您擁有一流的功能,您就不需要發明名稱調用術語來描述這一點。

當在函數中多次使用按名稱調用參數時,該參數會被多次計算。

由於傳入的參數應該是每個函數式編程的純函數調用,因此被調用函數中的每次評估將始終生成相同的結果。 因此,按名稱調用會比傳統的按值調用更浪費。

對於按名稱調用的日志記錄用例來說很好: https ://www.tutorialspoint.com/scala/functions_call_by_name.htm#:~:text=For%20this%20circumstance%2C%20Scala%20offers,and%20the% 20value%20is%20calculated

每次調用時都會評估按名稱調用。 但是如果它從未被調用,則永遠不會被評估。 因此,如果您的用例每次都可以接受或需要新的評估(例如從某個地方獲取新鮮/最新數據),那么它是有用的,並且如果從未使用過,則永遠不會被評估。 請參閱下面的代碼。 刪除“=>”,看看會發生什么。 在調用方法“m”之前只有一次調用。

object RunMe extends App {

  def m(b: Boolean, m: => String) = {
    if(b) m
  }

  def f = {
    println("hello world")
    "completed"
  }
  println("starting...")
  m(false, f)

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM