[英]Whether to use context bound or implicit ev in Scala
According to the style guide - is there a rule of thumb what one should use for typeclasses in Scala - context bound
or implicit ev
notation? 根据样式指南 - 是否有一个经验法则是什么应该用于Scala中的类型类 -
context bound
或implicit ev
符号?
These two examples do the same 这两个例子也是如此
Context bound has more concise function signature, but requires val
evaluation with implicitly
call: 上下文绑定具有更简洁的函数签名,但需要使用
implicitly
调用进行val
评估:
def empty[T: Monoid, M[_] : Monad]: M[T] = {
val M = implicitly[Monad[M]]
val T = implicitly[Monoid[T]]
M.point(T.zero)
}
The implicit ev
approach automatically inserts typeclasses into function parameters but pollutes method signature: implicit ev
方法自动将类型类插入函数参数,但污染方法签名:
def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] = {
M.point(T.zero)
}
Most of the libraries I've checked (eg "com.typesafe.play" %% "play-json" % "2.6.2"
) use implicit ev
我检查的大多数库(例如
"com.typesafe.play" %% "play-json" % "2.6.2"
)使用implicit ev
What are you using and why? 你在用什么?为什么?
One caveat you need to be aware of when working with implicitly
is when using dependently typed functions. 在
implicitly
使用时需要注意的一个警告是使用依赖类型的函数。 I'll quote from the book "The type astronauts guide to shapeless". 我将引用“宇航员类型宇航员指南”这本书。 It looks at the
Last
type class from Shapeless which retrieves the last type of an HList
: 它着眼于
Last
从无形型类检索的最后一个类型的HList
:
package shapeless.ops.hlist
trait Last[L <: HList] {
type Out
def apply(in: L): Out
}
And says: 并说:
The implicitly method from scala.Predef has this behaviour (this behavior means losing the inner type member information).
scala.Predef中的隐式方法具有此行为(此行为意味着丢失内部类型成员信息)。 Compare the type of an instance of Last summoned with implicitly:
比较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
to the type of an instance summoned with Last.apply:
到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
The type summoned by implicitly has no Out
type member, that is an important caveat and generally why you would use the summoner pattern which doesn't use context bounds and implicitly
. 被隐式召唤的类型没有
Out
类型成员,这是一个重要的警告,并且通常为什么你会使用不使用上下文边界和implicitly
的召唤器模式。
Other than that, generally I find that it is a matter of style. 除此之外,我发现这通常是一种风格问题。 Yes,
implicitly
might slightly increase compile times, but if you have an implicit rich application you'll most likely not "feel" the difference between the two at compile time. 是的,
implicitly
可能会稍微增加编译时间,但是如果你有一个隐式的富应用程序,你很可能在编译时“感觉”两者之间的差异。
And on a more personal note, sometimes writing implicitly[M[T]]
feels "uglier" than making the method signature a bit longer, and might be clearer to the reader when you declare the implicit explicitly with a named field. 而且更个人而言,有时候
implicitly[M[T]]
写implicitly[M[T]]
比使方法签名更长一些感觉更“丑陋”,并且当您使用命名字段明确声明隐式时,读者可能会更清楚。
This is very opinion-based, but one pratical reason for using an implicit parameter list directly is that you perform fewer implicit searches. 这是非常基于意见的,但直接使用隐式参数列表的一个实际原因是您执行的隐式搜索较少。
When you do 当你这样做
def empty[T: Monoid, M[_] : Monad]: M[T] = {
val M = implicitly[Monad[M]]
val T = implicitly[Monoid[T]]
M.point(T.zero)
}
this gets desugared by the compiler into 这被编译器贬低了
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)
}
so now the implicitly
method needs to do another implicit search to find ev1
and ev2
in scope. 所以现在
implicitly
方法需要进行另一次隐式搜索,以在范围内找到ev1
和ev2
。
It's very unlikely that this has a noticeable runtime overhead, but it may affect your compile time performance in some cases. 它不太可能具有明显的运行时开销,但在某些情况下它可能会影响编译时的性能。
If instead you do 相反,如果你这样做
def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] =
M.point(T.zero)
you're directly accessing M
and T
from the first implicit search. 你是从第一次隐式搜索直接访问
M
和T
Also (and this is my personal opinion) I prefer the body to be shorter, at the price of some boilerplate in the signature. 另外(这是我个人的意见)我更喜欢身体更短,签名中的一些样板价格。
Most libraries I know that make heavy use of implicit parameters use this style whenever they need to access the instance, so I guess I simply became more familiar with the notation. 我知道大多数库都会大量使用隐式参数,只要需要访问实例就会使用这种样式,所以我想我只是对符号更加熟悉。
Bonus, if you decide for the context bound anyway, it's usually a good idea to provide an apply
method on the typeclass that searches for the implicit instance. 奖励,如果你决定了上下文绑定,通常在类型类上提供一个搜索隐式实例的
apply
方法是个好主意。 This allows you to write 这允许你写
def empty[T: Monoid, M[_]: Monad]: M[T] = {
Monad[M].point(Monoid[T].zero)
}
More info on this technique here: https://blog.buildo.io/elegant-retrieval-of-type-class-instances-in-scala-32a524bbd0a7 有关此技术的更多信息,请访问: https : //blog.buildo.io/elegant-retrieval-of-type-class-instances-in-scala-32a524bbd0a7
Note that on top of doing the same, your 2 examples are the same. 请注意,除了这样做之外,您的两个示例是相同的。 Context bounds is just syntactic sugar for adding implicit parameters.
上下文边界只是添加隐式参数的语法糖。
I am being opportunistic, using context bound as much as I can ie, when I don't already have implicit function parameters. 我是机会主义者,尽可能多地使用上下文绑定,即当我还没有隐含的函数参数时。 When I already have some, it is impossible to use context bound and I have no other choice but adding to the implicit parameter list.
当我已经拥有一些时,就不可能使用上下文绑定,除了添加到隐式参数列表之外别无选择。
Note that you don't need to define val
s as you did, this works just fine (but I think you should go for what makes the code easier to read): 请注意,您不需要像您一样定义
val
,这很好用(但我认为您应该选择使代码更易于阅读的内容):
def empty[T: Monoid, M[_] : Monad]: M[T] = {
implicitly[Monad[M]].point(implicitly[Monoid[T]].zero)
}
FP libraries usually give you syntax extensions for typeclasses: FP库通常为类型类提供语法扩展:
import scalaz._, Scalaz._
def empty[T: Monoid, M[_]: Monad]: M[T] = mzero[T].point[M]
I use this style as much as possible. 我尽可能地使用这种风格。 This gives me syntax consistent with standard library methods and also lets me write
for
-comprehensions over generic Functor
s / Monad
s 这给了我与标准库方法一致的语法,也让我在泛型
Functor
s / Monad
编写for
理解
If not possible, I use special apply
on companion object: 如果不可能,我使用特殊的
apply
于伴侣对象:
import cats._, implicits._ // no mzero in cats
def empty[T: Monoid, M[_]: Monad]: M[T] = Monoid[T].empty.pure[M]
I use simulacrum to provide these for my own typeclasses. 我使用simulacrum为我自己的类型类提供这些。
I resort to implicit ev
syntax for cases where context bound is not enough (eg multiple type parameters) 对于上下文绑定不够的情况(例如多个类型参数),我使用
implicit ev
语法
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.