2017-08-31 53 views
3

根据样式指南 - 是否存在一个经验法则?Scala中的类别类型应该使用什么规则?context boundimplicit ev表示法?是否在Scala中使用上下文绑定或隐式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) 
} 

大部分的图书馆我che cked(例如"com.typesafe.play" %% "play-json" % "2.6.2")使用implicit ev

你在使用,为什么?

回答

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

+0

我完全同意,但我想补充一个小精度:隐式搜索是通过执行编译器在编译时,所以绝对没有运行时性能的影响。结论:这实际上就像你说“基于意见”一样。 –

+0

没有运行时性能影响,但编译时性能在scala代码库中有点问题,所以我总是倾向于留意它;)也可能有一点运行时间的开销,因为你正在做一个额外的方法调用(隐含''),但是JVM可能会嵌入那个 –

+0

谢谢!用'apply'方法真的很好,检查了文章。虽然没有在'scalaz'中找到,但是 –

1

注意,在做同样的,你的2个例子相同的顶部。上下文边界只是用于添加隐式参数的语法糖。

我是机会主义者,尽可能使用上下文绑定,即当我没有隐式函数参数时。当我已经有一些,不可能使用上下文绑定,我没有其他选择,只是添加到隐式参数列表。

注意,你不需要定义val S作为你这样做,这只是罚款(但我认为你应该去什么使代码更易于阅读):

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

谢谢,我明白它是如何工作的,问题只是关于代码风格。这是一个口味的问题,我相信,但想与社区检查:) –

3

一个警告你当使用implicitly时,需要注意使用依赖类型的函数。我会引用这本书“宇航员指导不成形”。它着眼于从无形的Last型类检索的最后一个类型的HList的:

package shapeless.ops.hlist 

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

,并说:

的隐式地从scala.Predef方法有这种行为(这 行为意味着失去内部类型成员信息)。在 类型最后的实例与含蓄召见比较:

implicitly[Last[String :: Int :: HNil]] 
res6: shapeless.ops.hlist.Last[shapeless.::[String,shapeless 
     .::[Int,shapeless.HNil]]] = [email protected] 

与Last.apply召唤一个实例的类型:

Last[String :: Int :: HNil] 
res7: shapeless.ops.hlist.Last[shapeless.::[String,shapeless 
     .::[Int,shapeless.HNil]]]{type Out = Int} = [email protected] 

类型传唤通过隐含没有Out类型成员,这是一个重要的警告,并且通常为什么你会使用不使用上下文边界和implicitly的召唤者模式。


除此之外,通常我会发现它是一个风格问题。是的,implicitly可能会稍微增加编译时间,但是如果您有一个隐含的丰富应用程序,那么在编译时您很可能不会“感觉到”两者之间的差异。

而在更个人的笔记中,有时写implicitly[M[T]]会感觉“丑陋”而不是使方法签名更长一些,并且当您使用命名字段显式声明隐式时,读者可能会更清楚。

+0

谢谢!将检查这本书,不知道这个警告 –

+0

@EugeneZhulkov伟大的书一般为类型级别编程和无形的介绍。 –

1

FP库通常给你的语法扩展类型类:

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

我用这种风格尽可能地。这给我的语法使用标准库的方法一致,也让我写/ for -comprehensions超过通用Functor小号Monad小号


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

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

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


我诉诸implicit ev语法情况下,势必方面是不够的(如多个类型参数)

+0

哦,谢谢你!完全忘了'mzero [T] .point [M]'。但我的问题更笼统,不仅关于fp库,还关于所有类型类。 –

+0

@EugeneZhulkov,是的,我描述了我的偏好,没有风格指南。很多库实际上提供了语法丰富(例如'spire'中的运算符)或具有隐式参数的常规方法(例如'play-json'中的'Json.toJson(instance)')允许您使用上下文边界,所以函数定义并且实现简短且可读。对于自定义的,它是关于添加一个单一的注释特性。 –

相关问题