scala - 使用带有一些基类、抽象类或特征参数化列表的类型类的最佳方法

标签 scala inheritance typeclass

我认为用具体的例子来描述问题会更容易。假设我有 Fruit类层次结构和 Show类型类:

trait Fruit
case class Apple extends Fruit
case class Orange extends Fruit

trait Show[T] {
    def show(target: T): String
}

object Show { 
    implicit object AppleShow extends Show[Apple] {
        def show(apple: Apple) = "Standard apple"
    }

    implicit object OrangeShow extends Show[Orange] {
        def show(orange: Orange) = "Standard orange"
    }
}

def getAsString[T](target: T)(implicit s: Show[T]) = s show target

我还有我想使用 Show 向用户展示的水果列表(这是我在这个问题中的主要目标):
val basket = List[Fruit](Apple(), Orange())

def printList[T](list: List[T])(implicit s: Show[T]) = 
    list foreach (f => println(s show f))

printList(basket)

这不会编译,因为 List参数化为 Fruit而我还没有定义任何 Show[Fruit] . 使用类型类实现我的目标的最佳方法是什么?

我试图为这个问题找到解决方案,但不幸的是还没有找到任何好的解决方案。光知道还不够sprintList函数 - 不知何故它需要知道 Show[T]对于列表的每个元素。这意味着,为了能够做到这一点,除了编译时机制之外,我们还需要一些运行时机制。这让我了解了某种运行时字典,知道如何找到通讯员 Show[T]在运行时。

隐式实现 Show[Fruit]可以作为这样的字典:
implicit object FruitShow extends Show[Fruit] {
    def show(f: Fruit) = f match {
        case a: Apple => getAsString(a)
        case o: Orange => getAsString(o)
    }
}

实际上,在haskell 中可以找到非常相似的方法。例如,我们可以查看 Eq Maybe 的实现:
instance (Eq m) => Eq (Maybe m) where  
    Just x == Just y = x == y  
    Nothing == Nothing = True  
    _ == _ = False  

这个解决方案的大问题是,如果我添加 Fruit 的新子类像这样:
case class Banana extends Fruit

object Banana {
    implicit object BananaShow extends Show[Banana] {
        def show(banana: Banana) = "New banana"
    }
}

并将尝试打印我的篮子:
val basket = List[Fruit](Apple(), Orange(), Banana())

printList(basket)

然后 scala.MatchError会被抛出,因为我的字典对香蕉一无所知。当然,我可以在一些了解香蕉的上下文中提供更新的字典:
implicit object NewFruitShow extends Show[Fruit] {
    def show(f: Fruit) = f match {
        case b: Banana => getAsString(b)
        case otherFruit => Show.FruitShow.show(otherFruit)
    }
}

但这个解决方案远非完美。想象一下,其他一些图书馆为另一种水果提供了它自己的字典版本。它只会与 NewFruitShow 冲突如果我尝试将它们一起使用。

也许我错过了一些明显的东西?

更新

正如@Eric 所注意到的,这里还描述了另一种解决方案:forall in Scala .它看起来真的很有趣。但我发现此解决方案存在一个问题。

如果我使用 ShowBox ,那么它会在创建时记住具体的类型类。所以我通常使用对象和对应类型类构建列表(因此列表中存在字典)。另一方面,scala 有一个非常好的特性:我可以在当前作用域中删除新的隐式,它们将覆盖默认值。所以我可以为类定义替代字符串表示,例如:
object CompactShow { 
    implicit object AppleCompactShow extends Show[Apple] {
        def show(apple: Apple) = "SA"
    }

    implicit object OrangeCompactShow extends Show[Orange] {
        def show(orange: Orange) = "SO"
    }
}

然后使用 import CompactShow._ 在当前范围内导入它.在这种情况下 AppleCompactShowOrangeCompactShow对象将被隐式使用,而不是在 Show 的伴随对象中定义的默认值。 .您可以猜到,列表的创建和打印发生在不同的地方。如果我会使用 ShowBox ,我很可能会捕获类型类的默认实例。我想在最后一刻捕捉它们——我打电话的那一刻 printList ,因为我什至不知道,是否我的List[Fruit]在创建它的代码中将永远显示或如何显示。

最佳答案

最明显的答案是使用 sealed trait Fruit和一个 Show[Fruit] .这样你的模式匹配会在编译时提示,当匹配不是详尽的。当然,添加一种新的Fruit在外部库中是不可能的,但这是事物的本质所固有的。这是“expression problem”。

您也可以粘贴 Show Fruit 特性的实例:

trait Fruit { self =>
  def show: Show[self.type]
}

case class Apple() extends Fruit { self =>
  def show: Show[self.type] = showA
}

或者,您知道,停止子类型并改用类型类。

关于scala - 使用带有一些基类、抽象类或特征参数化列表的类型类的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7264824/

相关文章:

c++ - Impossible expected class-name before ‘{’ token 错误解决

haskell - 使用关联类型族时推断类型类约束

scala - AST 转换对象如何工作?

scala - 值类中的 If 语句

c# - 使用鉴别器的 Fluent NHibernate 的多级继承

haskell - 在 Haskell 中,Integral typeclass 是否意味着 Show typeclass?

haskell - 无法从上下文中推导出 (Eq a) (...)

scala - 来自 Spark DataFrame/RDD 的前 N ​​个项目

java - Scala/Java 项目在 Maven 编译期间找不到依赖项

c++继承私有(private)复制构造函数: how doesn't this yield a compile time error?