繁体   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