簡體   English   中英

是否在Scala中使用上下文綁定或隱式ev

[英]Whether to use context bound or implicit ev in Scala

根據樣式指南 - 是否有一個經驗法則是什么應該用於Scala中的類型類 - context boundimplicit ev符號?

這兩個例子也是如此

上下文綁定具有更簡潔的函數簽名,但需要使用implicitly調用進行val評估:

def empty[T: Monoid, M[_] : Monad]: M[T] = {
    val M = implicitly[Monad[M]]
    val T = implicitly[Monoid[T]]
    M.point(T.zero)
}

implicit ev方法自動將類型類插入函數參數,但污染方法簽名:

def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] = {
  M.point(T.zero)
}

我檢查的大多數庫(例如"com.typesafe.play" %% "play-json" % "2.6.2" )使用implicit ev

你在用什么?為什么?

implicitly使用時需要注意的一個警告是使用依賴類型的函數。 我將引用“宇航員類型宇航員指南”這本書。 它着眼於Last從無形型類檢索的最后一個類型的HList

package shapeless.ops.hlist

trait Last[L <: HList] {
  type Out
  def apply(in: L): Out
}

並說:

scala.Predef中的隱式方法具有此行為(此行為意味着丟失內部類型成員信息)。 比較Last Sumed實例的類型與隱式:

implicitly[Last[String :: Int :: HNil]]
res6: shapeless.ops.hlist.Last[shapeless.::[String,shapeless
      .::[Int,shapeless.HNil]]] = shapeless.ops.hlist$Last$$anon$34@20bd5df0

到Last.apply召喚的實例的類型:

Last[String :: Int :: HNil]
res7: shapeless.ops.hlist.Last[shapeless.::[String,shapeless
      .::[Int,shapeless.HNil]]]{type Out = Int} = shapeless.ops.hlist$Last$$anon$34@4ac2f6f

被隱式召喚的類型沒有Out類型成員,這是一個重要的警告,並且通常為什么你會使用不使用上下文邊界和implicitly的召喚器模式。


除此之外,我發現這通常是一種風格問題。 是的, implicitly可能會稍微增加編譯時間,但是如果你有一個隱式的富應用程序,你很可能在編譯時“感覺”兩者之間的差異。

而且更個人而言,有時候implicitly[M[T]]implicitly[M[T]]比使方法簽名更長一些感覺更“丑陋”,並且當您使用命名字段明確聲明隱式時,讀者可能會更清楚。

這是非常基於意見的,但直接使用隱式參數列表的一個實際原因是您執行的隱式搜索較少。

當你這樣做

def empty[T: Monoid, M[_] : Monad]: M[T] = {
  val M = implicitly[Monad[M]]
  val T = implicitly[Monoid[T]]
  M.point(T.zero)
}

這被編譯器貶低了

def empty[T, M[_]](implicit ev1: Monoid[T], ev2: Monad[M]): M[T] = {
  val M = implicitly[Monad[M]]
  val T = implicitly[Monoid[T]]
  M.point(T.zero)
}

所以現在implicitly方法需要進行另一次隱式搜索,以在范圍內找到ev1ev2

它不太可能具有明顯的運行時開銷,但在某些情況下它可能會影響編譯時的性能。

相反,如果你這樣做

def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] =
  M.point(T.zero)

你是從第一次隱式搜索直接訪問MT

另外(這是我個人的意見)我更喜歡身體更短,簽名中的一些樣板價格。

我知道大多數庫都會大量使用隱式參數,只要需要訪問實例就會使用這種樣式,所以我想我只是對符號更加熟悉。


獎勵,如果你決定了上下文綁定,通常在類型類上提供一個搜索隱式實例的apply方法是個好主意。 這允許你寫

def empty[T: Monoid, M[_]: Monad]: M[T] = {
  Monad[M].point(Monoid[T].zero)
}

有關此技術的更多信息,請訪問: https//blog.buildo.io/elegant-retrieval-of-type-class-instances-in-scala-32a524bbd0a7

請注意,除了這樣做之外,您的兩個示例相同的。 上下文邊界只是添加隱式參數的語法糖。

我是機會主義者,盡可能多地使用上下文綁定,即當我還沒有隱含的函數參數時。 當我已經擁有一些時,就不可能使用上下文綁定,除了添加到隱式參數列表之外別無選擇。

請注意,您不需要像您一樣定義val ,這很好用(但我認為您應該選擇使代碼更易於閱讀的內容):

def empty[T: Monoid, M[_] : Monad]: M[T] = {
  implicitly[Monad[M]].point(implicitly[Monoid[T]].zero)
}

FP庫通常為類型類提供語法擴展:

import scalaz._, Scalaz._
def empty[T: Monoid, M[_]: Monad]: M[T] = mzero[T].point[M]

我盡可能地使用這種風格。 這給了我與標准庫方法一致的語法,也讓我在泛型Functor s / Monad編寫for理解


如果不可能,我使用特殊的apply於伴侶對象:

import cats._, implicits._ // no mzero in cats
def empty[T: Monoid, M[_]: Monad]: M[T] = Monoid[T].empty.pure[M]

我使用simulacrum為我自己的類型類提供這些。


對於上下文綁定不夠的情況(例如多個類型參數),我使用implicit ev語法

暫無
暫無

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

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