[英]Whether to use context bound or implicit ev in Scala
根據樣式指南 - 是否有一個經驗法則是什么應該用於Scala中的類型類 - context bound
或implicit 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
方法需要進行另一次隱式搜索,以在范圍內找到ev1
和ev2
。
它不太可能具有明顯的運行時開銷,但在某些情況下它可能會影響編譯時的性能。
相反,如果你這樣做
def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] =
M.point(T.zero)
你是從第一次隱式搜索直接訪問M
和T
另外(這是我個人的意見)我更喜歡身體更短,簽名中的一些樣板價格。
我知道大多數庫都會大量使用隱式參數,只要需要訪問實例就會使用這種樣式,所以我想我只是對符號更加熟悉。
獎勵,如果你決定了上下文綁定,通常在類型類上提供一個搜索隱式實例的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.