简体   繁体   English

是否在Scala中使用上下文绑定或隐式ev

[英]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 boundimplicit 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方法需要进行另一次隐式搜索,以在范围内找到ev1ev2

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. 你是从第一次隐式搜索直接访问MT

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM