首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >TypeClasses in ScalaZ

TypeClasses in ScalaZ
EN

Stack Overflow用户
提问于 2017-11-17 18:59:32
回答 1查看 74关注 0票数 2

我正在读http://eed3si9n.com/learning-scalaz/a+Yes-No+typeclass.html,现在我读的是Yes-No type类的部分。最终的目标是让1.truthyreturn true。下面是类型化的实现:

代码语言:javascript
复制
trait CanTruthy[A] { self =>
  /** @return true, if `a` is truthy. */
  def truthys(a: A): Boolean
}
object CanTruthy {
  def apply[A](implicit ev: CanTruthy[A]): CanTruthy[A] = ev
  def truthys[A](f: A => Boolean): CanTruthy[A] = new CanTruthy[A] {
    def truthys(a: A): Boolean = f(a)
  }
}
trait CanTruthyOps[A] {
  def self: A
  implicit def F: CanTruthy[A]
  final def truthy: Boolean = F.truthys(self)
}
object ToCanIsTruthyOps {
  implicit def toCanIsTruthyOps[A](v: A)(implicit ev: CanTruthy[A]) =
    new CanTruthyOps[A] {
      def self = v
      implicit def F: CanTruthy[A] = ev
    }
}

implicit val intCanTruthy: CanTruthy[Int] = CanTruthy.truthys({
         case 0 => false
         case _ => true
       })

我觉得有点吓人。我们引入了两个新的特性来实现这一点。但是,我们可以通过使用隐式类来实现相同的目标:

代码语言:javascript
复制
trait CanTruthy {
  def truthy: Boolean
}

object CanTruthy{
  implicit class CanTruthyInt(i: Int) extends CanTruthy{
    override def truthy: Boolean = i match {
      case 0 => false
      case _ => true
    }
  }
}

在我看来是一样的。那么,为什么我们需要在教程中使用这种方式呢?我错过了什么?你能解释一下有什么区别吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-11-26 04:32:14

我认为这里的问题是误解了这句话的范围:

最终的目标是让1.truthy返回true

这就是我们试图对CanTruthyOps类做的事情,但它不是CanTruthy类型类的目标,更普遍的说,类似于这类的语法问题并不是类型类的目标。

类型类的目标是允许我们以简单、灵活、组合的方式约束类型。类型参数少的CanTruthy方法实际上并不能很好地支持简单的部分或灵活的部分或组合部分(可以说,Scala中的类型类的实现也不是很简单,但它至少更简单,而且肯定更灵活和更复杂)。

以教程中的这个方法为例(为了避免Any,略作修改):

代码语言:javascript
复制
// Type class style
def truthyIf[A: CanTruthy, B](cond: A)(ifyes: => B)(ifno: => B): B =
  if (cond.truthy) ifyes else ifno

如果您想将其转换为没有类型的类型参数样式,那么在开始时似乎很不错:

代码语言:javascript
复制
// Parameterless style
def truthyIf[B](cond: CanTruthy)(ifyes: => B)(ifno: => B): B =
  if (cond.truthy) ifyes else ifno

但是现在假设您需要保留原始类型。这可能有很多原因--例如,在检查其中一个值的真实性之前,您可能希望使用scala.Ordering对值集合进行排序,或者您可能有此方法的一个变体,其中原始类型也是返回类型(此处为类型类样式):

代码语言:javascript
复制
// Type class style
def truthyOrElse[A: CanTruthy](cond: A)(ifno: => A): A =
  if (cond.truthy) cond else ifno

现在翻译就不那么有趣了:

代码语言:javascript
复制
// Type parameter-less style
def truthyOrElse[A <% CanTruthy](cond: A)(ifno: => A): A =
  if (cond.truthy) ifyes else ifno

其中,时髦的<%是隐式参数的语法糖:

代码语言:javascript
复制
// Type parameter-less style (desugared)
def truthyOrElse[A](cond: A)(ifno: => A)(implicit evidence$1: A => CanTruthy): A =
  if (cond.truthy) cond else ifno

但是,类型类样式中的:也是语法糖:

代码语言:javascript
复制
// Type class style, desugared
def truthyOrElse[A](cond: A)(ifno: => A)(implicit evidence$2: CanTruthy[A]): A =
  if (cond.truthy) cond else ifno

请注意,这些方法看起来几乎相同--在这两种方法中,都需要一些隐式证据(在编译时)来证明A是真实的。在类型参数少样式中,此证据是隐式转换,而在类型类样式中,则是泛型类型的隐式值。

后一种方法有几个优点。一种抽象之一是,它允许我们将“这是我知道如何对这种类型做X的一些证据”与纯粹的句法关系区分开来,我可以在这件事上调用.x。当然,这种分离需要一些额外的机制(两个特征而不是一个),但是在语法和语义问题之间保持一条清晰的线是值得的。

另一个(相关的)优点是类型类可以更高效,因为它允许我们放弃语法,因此它还涉及到额外的分配:

代码语言:javascript
复制
// Type class style, no syntax
def truthyOrElse[A](cond: A)(ifno: => A)(implicit ev: CanTruthy[A]): A =
  if (ev.truthys(cond)) cond else ifno

在您试图提供证据的操作涉及多个值的情况下,另一个优点是:

代码语言:javascript
复制
trait Addable[A] {
  def plus(a: A, b: A): A
}

object Addable {
  implicit val intAddable: Addable[Int] = new Addable[Int] {
    def plus(a: Int, b: Int): Int = a + b
  }
}

作为Int => Addable隐式转换,没有一种很好的方法来完成这种事情。

类型类方法同样处理需要操作的多个类型的情况,而类型参数-less方法实际上并不是这样(至少不是以任何相当干净的方式)。

因此,总结一下:如果您只想要一些在有具体类型的情况下通常使用的很好的充实方法,那么类型参数少的方法是完全合理的,并且可能需要更少的代码。如果您希望能够以高效、灵活、通用和合理优雅的方式抽象支持某些操作的类型,请编写一个类型类。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47357537

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档