我一直在作業中使用 Scala,并且我有一個與隱式引數有關的問題。
我經常executionContext在方法定義和類定義中看到定義。同時,我還看到了接受包含配置資料(超時、配接器、埠等)作為常規引數的案例類的類。
我的問題是為什么在傳遞配置時這個引數沒有被定義為隱式?或者相反,如果executionContext將其定義為常規引數呢?我試圖了解何時使用隱式引數以及何時不使用它們。
編輯:也許通過案例類的例子不是最好的例子,這是我想到的第一個想法
uj5u.com熱心網友回復:
從概念上講,隱式是應用程式邏輯“外部”的東西,而顯式引數是……嗯……顯式的。
考慮一個函式def f(x: Double): Double = x*x
它是一個將給定實數轉換為另一個實數的純函式。作為一個顯式引數是有意義的x,因為它是這個函式的內在部分。
現在,假設您正在實作某種近似的乘法演算法,并且想要控制您的函式計算答案的精度。你可以做def f(x: Double, precision: Int): Double = ???。它會起作用,但不方便且有點笨拙:
- 函式定義不再表達函式的概念“性質”是對實數集的純變換
- 這使得呼叫現場變得復雜,因為使用您的函式的每個人現在都必須知道要傳遞的這個附加引數(想象一下,您正在撰寫一個庫供非工程數學專業的學生使用,他們了解抽象轉換和復雜的公式,但可能不太關心數值精度:當您需要計算正方形的面積時,您多久考慮一次精度?)。
- 它還使現有代碼更難閱讀和修改
所以,為了讓它更漂亮,你可以做def f(x: Double)(implicit precision: Int) = ???. 這樣做的好處是可以準確說出您想要的內容:“我有一個轉換double => double,它將在計算實際結果時使用隱含的精度)。那些數學專業的學生現在可以按照他們習慣的方式撰寫他們的抽象公式:val area = square(x)不污染他們的帶有他們并不真正關心的煩人配置的邏輯。
當然,何時準確地使用它是一個意見和品味的問題(這在 SO 上是明確禁止的)。有人當然可以對上面的例子爭論,這precision實際上是轉換定義的一部分,因為5.429和5.54289(分別是和的結果f(2.33)(3))f(2.33)(4)是兩個不同的數字。
所以,歸根結底,你只需要運用你的判斷力和常識來為你遇到的每一個案例做出決定。
使用現有庫時,還有另一個考慮因素。考慮:
def foo(f: Future[Int], ec: ExecutionContext) =
f.map { x => x*x }(ec)
.map { _.toString } (ec)
.foreach(println)(ec)
如果你隱含了,這看起來會更好,也不會那么混亂ec,無論你在哲學上站在哪里考慮是否將其視為你轉型的一部分:
def foo(f: Future[Int])(implicit ec: ExecutionContext) =
f.map { x => x*x }.map(_.toString).foreach(println)
uj5u.com熱心網友回復:
在以下情況下可以使用隱式:
- 你只需要某種型別的一個值
- 如何定義這樣的值是明確的
- 這包括手動定義以及使用元編程來生成值,例如它的型別是如何定義的
Futures 和 Akka 決定將一些“全域變數”作為隱式傳遞是一個合理的用例,因此它們將作為隱式傳遞:
ExecutionContextActorSystem,Materializer- 各種配置,例如
Timeout
一般來說,您不想放入某個靜態欄位,但會到處傳遞。
然而,Scala 世界的其余部分將通過使用一些抽象來解決這個問題,這些抽象將在引擎蓋下傳遞這些東西,某種構建器,通過構造(dependencies) => result函式,函式抽象等。
例如cats.effect.IO,不需要傳遞ExecutionContext,因為它會在您運行時傳遞其調度程式。只有當您想顯式更改正在運行的池時,您才必須使用某種方法。在 Monix 中運行的東西也需要你Scheduler在最后通過,當整個計算組成時。所以這兩種走近都讓你放棄了傳遞所有這些ExecutionContexts。在 Future 的情況下,這是必要的,因為您需要控制執行緒池,但您也急切地評估事物,并且手動放置ec( ) 會破壞理解。futureA.flatMap(f)(ec)
因此,在 Akka 生態系統之外和 rawFuture更常用于攜帶型別類,作為將業務邏輯與特定實作分離的一種手段,允許在不修改使用這些實作的代碼的情況下添加對新型別的支持,等等. (Scala 中有大量型別類的示例,因此我將在此處跳過)。
通常,當我讀到人們使用implicits 來傳遞配置時,它以悲傷告終只是時間問題。Akka 和 EC 有點需要它們,但你應該明確地傳遞配置。您可以將它們分組為case classes 以傳遞一堆它們,這不是什么大問題。您還可以將所有需要的東西作為隱式顯式放在一個地方并執行以下操作:
case class Configs(dbEX: EC, mapEC: EC)
class SomeBehavior(configs: Configs) {
def someAction = {
if (...) {
implicit val ec: EC = configs.dbEC
...
} else {
implicit val ec: EC = configs.mapEC
...
}
}
}
使它們僅在需要它們的地方隱含。一個好的經驗是:你是否關心是否有一些你在代碼中看不到的東西傳遞過來?通常,答案是,是的,你愿意,你更愿意看到它,只有例外情況是價值從何而來有點明顯,或者你有點知道價值沒問題而你沒有不用管它是從哪里來的。
uj5u.com熱心網友回復:
在 Scala中有大量的用例implicit:在底層,它們歸結為利用編譯器的隱式決議機制來填充可能沒有明確提及的東西,但是用例在 Scala 3 中足夠不同,每個用例(那些在 Scala 3 中幸存的用例......)都使用不同的關鍵字進行編碼。
在執行背景關系的情況下,implicit引數被用于在通常是靜態范圍的語言中模擬動態范圍。這樣做的主要好處是,它允許在呼叫堆疊的更下方決定行為,而不必總是通過堆疊的中間層顯式傳遞行為(同時為那些干預層干凈利落地強制不同的行為)。
從歷史上看,這方面的一個主要例子是數值精度。許多數值運算最終是通過迭代細化來實作的(例如,當平方根在軟體中實作時,它可能使用牛頓法實作),這意味著在計算速度和精度(暗示精度)之間存在權衡。使用動態范圍,有一種巧妙的方法可以做到這一點:一個全域變數,用于在數學結果中達到所需的精度水平。您的數字例程檢查該變數的值并相應地進行自我管理。與靜態作用域語言中全域變數的區別在于,當 A 呼叫 B 而呼叫 C 時,如果 A 將值設定x為 1,B 將其設定為 2,x則在 C 或 B 中檢查時將為 2,但一旦 B 回傳一個,x 將再次為 1(在動態范圍的語言中,您可以將全域變數視為實際上是堆疊值的名稱,并且語言實作會根據需要自動彈出堆疊)。
動態作用域曾經相當流行(尤其是在 1970 年代中后期之前的 Lisps 中);如今,您真正看到它的唯一地方是 Bourne shell(包括 bash)、Emacs Lisp;而某些語言(Perl 和 Common Lisp 可能是兩個主要示例)是混合語言:變數以特殊方式宣告以使其具有動態或靜態作用域。靜態作用域顯然贏得了勝利:語言實作或程式員更容易推理。
這種輕松的代價是,在我們的數值計算示例中,我們最終得到如下內容:
def newtonSqrt(x: Double, precision: Int): Double = ???
/** Calculates the length of the hypotenuse of a right triangle with legs of given lengths
*/
def hypotenuse(x: Double, y: Double, precision: Int): Double =
newtonSqrt(x*x y*y, precision)
值得慶幸的是,Scala 支持默認引數,因此我們也避免了使用默認精度的版本。可以說,precision它暴露了一個實作細節(事實上我們的計算不一定在數學上完全準確):重要的是斜邊的長度是腿的平方和的平方根。
在 Scala 中,我們可以將精度設為隱式:
// DON'T ACTUALLY PASS AN INT IMPLICITLY!!!!!!
def newtonSqrt(x: Double)(implicit precision: Int): Double = ???
def hypotenuse(x: Double, y: Double)(implicit precision: Int): Double =
newtonSqrt(x*x, y*y)
(實際上,除了通過隱式機制描述所討論的行為之外,傳遞一個原始或任何可能用于其他事情的型別實際上是非常糟糕的:我在這里這樣做是為了教學清晰)。
編譯器將有效地轉換newtonSqrt(x*x y*y)為(非常相似的東西)newtonSqrt(x*x y*y, precision)。現在呼叫者hypotenuse可以決定precision通過 an來修復implicit val或通過將隱式添加到他們的簽名來推遲選擇給呼叫者。
動態范圍長期以來一直存在爭議,因此即使是受約束的動態范圍,這種implicit嵌入的使用也存在爭議也就不足為奇了。在 Scala 的情況下,在許多情況下,工具在幫助您找出隱式時會伸出手來,這無濟于事:遇到的大多數真正令人憤怒的編譯器錯誤都與缺少隱式或沖突有關,并且跟蹤到圖在任何時候找出哪些值在隱含范圍內并不是工具有幫助人們的歷史。因此,有許多開發人員認為通過配置顯式執行緒化優于使用隱式。
這種行為描述最好是隱式傳遞還是顯式傳遞,這在很大程度上取決于品味和情況(值得注意的是,型別類模式,特別是對連貫性沒有硬性要求(只有一種可能的方式)描述行為)在 Scala 中是典型的,只是這種行為描述的一個特例)。
我還應該注意,將一些設定捆綁到案例類與隱式傳遞它們之間不是二元選擇:您可以同時執行以下操作:
case class ProcessSettings(sys: ActorSystem, ec: ExecutionContext)
object ProcessSettings {
implicit def implicitly(implicit sys: ActorSystem, ec: ExecutionContext): ProcessSettings =
ProcessSettings(sys, ec)
}
def doStuff(x: SomeInput)(implicit settings: ProcessSettings)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/415348.html
標籤:
上一篇:微服務架構 | *3.5 Nacos 服務注冊與發現的原始碼分析
下一篇:“有效尾遞回”是什么意思?
