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

时间:2017-08-31 09:29:12

标签: scala

根据样式指南 - 是否有一个经验法则是什么应该用于Scala中的类型类 - //say we get the value 'a,b,c' from localStorage into the temp variable //var temp = localStorage.getItem(profskill); var temp= 'a,b,c'; this.getskills = temp.split(','); console.log(this.getskills[0]); context bound表示法?

这两个例子做同样的事情

上下文绑定具有更简洁的功能签名,但需要使用implicit ev调用进行val评估:

implicitly

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"

你在用什么?为什么?

4 个答案:

答案 0 :(得分:3)

使用implicitly时需要注意的一点是使用依赖类型函数时。我将引用这本书"类型宇航员指导无形"。它查看了Shapeless中的Last类型类,它检索HList的最后一种类型:

package shapeless.ops.hlist

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

并说:

  

scala.Predef中的隐式方法有这种行为(这个   行为意味着丢失内部类型成员信息)。比较   Last隐藏的最后一个实例的类型:

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可能会略微增加编译时间,但如果您有一个隐含的丰富应用程序,那么您很可能不会感觉到#34;编译时两者之间的差异。

从更个人的角度来说,有时写implicitly[M[T]]感觉&#34; uglier&#34;比使方法签名更长一些,并且当您使用命名字段明确声明隐式时,读者可能会更清楚。

答案 1 :(得分:2)

这是非常基于意见的,但直接使用隐式参数列表的一个实际原因是您执行的隐式搜索次数较少。

当你这样做时

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

答案 2 :(得分:1)

请注意,除此之外,您的2个示例 相同。上下文边界只是添加隐式参数的语法糖。

我是机会主义者,尽可能多地使用上下文绑定,即,当我还没有隐含的函数参数时。当我已经拥有一些时,就不可能使用上下文绑定,除了添加到隐式参数列表之外别无选择。

请注意,您不需要像您一样定义val,这样做很好(但我认为您应该选择使代码更易于阅读的内容):

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

答案 3 :(得分:1)

FP库通常为类型类提供语法扩展:

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

我尽可能使用这种风格。这使我的语法与标准库方法一致,也让我写for - 对通用Functor s / Monad s的理解

如果不可能,我在伴侣对象上使用特殊apply

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

我使用simulacrum为我自己的类型类提供这些。

对于上下文绑定不够的情况(例如多个类型参数),我采用implicit ev语法