如何保证伴随对象中存在方法并引用它?

时间:2014-11-13 00:30:31

标签: scala generics traits companion-object type-bounds

考虑这个例子,其中Listable旨在混合到案例类的伴随对象中。因此,要调用Writer.grid,必须有一个扩展A的伴随对象Listable[A],其中定义了implicit Writer[A]。 (例如,将任意Listable的列表转换为CSV格式。)

trait Listable[A] {
    def list: List[A]
}

object Writer {
    def grid[A <: Listable[A]](listable: A)(implicit w: Writer[A]): String = {  
        listable.list.map(w.write(_).mkString(",")).mkString("\n")
    }
}

trait Writer[A] {
    def write(a: A): List[String]
}

这是一个天真的实现:

case class Test(id: Int, text: String)

object Test extends Listable[Test] {
    def list = List(Test(1, "test"))

    implicit val wrt = new Writer[Test] {
        def write(t: Test) = List(t.id.toString, t.text)
    }
}

这是编译,但无法正常工作,因为listable: A实际上引用了对象TestA中的w: Writer[A]引用了案例类Test,所以调用Writer.grid(Test)无法符合类型边界。

我可以通过放弃Listable并在implicit List[A]的签名中要求grid来解决此问题:

def grid[A](implicit w: Writer[A], list: List[A]): String = ...

但我更愿意:

  1. 不需要可能产生意外结果的隐式函数。
  2. 不使用特殊类型来包装list,因为它也会在别处使用。
  3. grid方法的定义保留在Listable之外。
  4. 是否可以重新设置Writer.grid的签名才能使其正常工作? (或其他结构变化)

2 个答案:

答案 0 :(得分:1)

你想要一些奇怪的东西。案例类测试明显具有类型Test,但是伴随对象Test与类型&#39; Test&#39;没有任何关系,它有一种类型&#39; Test.type&#39; 。

我建议你制作2个合适的类型:Listable和Writer

  trait Listable[A] {
    def list: List[A]
  }

  object Writer {
    def grid[A : Listable : Writer](listable: A): String = {
      implicitly[Listable[A]].list.map(implicitly[Writer[A]].write(_).mkString(",")).mkString("\n")
    }
  }

  trait Writer[A] {
    def write(a: A): List[String]
  }

  case class Test(id: Int, text: String)

  object Test {
    implicit val listable: Listable[Test] = new Listable[Test] {
       def list: List[Test] = List(Test(1, "test"))
    }

    implicit val wrt: Writer[Test] = new Writer[Test] {
      def write(t: Test) = List(t.id.toString, t.text)
    }
  }

  Writer.grid(Test(123, "abc"))

答案 1 :(得分:1)

基本上Engene指出了一个有效的解决方案,我只是想说,你的情况真的很奇怪,你不需要网格函数中的listable实例。从你的原始代码中可以看出这个事实并不明显,但是如果你从Eugene的代码中看到这个问题就完全没有了,那么这个论点根本就不用了。

所以我猜你要做的是让伴侣对象为特殊目的设置一个特殊实例的实例,也许我错了。但名称Listable[A]确实令人困惑,如果你试图描述伴侣对象的某些特征,你可以命名为ListableMeta[A],这更有意义。

trait ListableMeta[A] {
    def list: List[A]
}

object Writer {
    def grid[A : ListableMeta](implicit w: Writer[A]): String = {
        implicitly[ListableMeta[A]].list.map(w.write(_).mkString(",")).mkString("\n")
    }
}

trait Writer[A] {
    def write(a: A): List[String]
}

case class Test(id: Int, text: String)

trait InImplicit {
    implicit val injectThisToImplicits: this.type = this
}

object Test extends ListableMeta[Test] with Writer[Test] with InImplicit {
    def list = List(Test(1, "test"))

    def write(t: Test) = List(t.id.toString, t.text)
}

你需要做的基本上是Writer.grid[Test]而不是传递一个具体的实例,所以这应该工作。 但这里明确的要点是在所有事情上使用类型类,混合和匹配类型类和类型边界会引起混淆。