【问题标题】:How to implement an heterogeneous container in Scala如何在 Scala 中实现异构容器
【发布时间】:2014-10-25 08:24:40
【问题描述】:

我需要一个异构、类型安全的容器来存储不相关的类型 A、B、C。

这是一种类型级别的规范:

trait Container {
  putA(a: A) 
  putB(b: B)
  putC(c: C)
  put(o: Any) = { o match {
    case a: A => putA(a)
    case b: B => putB(b)
    case c: C => putC(c)
  }
  getAllAs : Seq[A]
  getAllBs : Seq[B]
  getAllCs : Seq[C]
}

哪种类型最适合支持此容器?

是否值得为 A、B、C 类型创建一个 Containerable[T] 类型类?

谢谢。

【问题讨论】:

  • 如果AB 中的任何一个碰巧相同,put(o: Any) 不是安全操作。
  • 我假设 ABC 不是泛型类型,因为那样的话,在运行时,它们将被擦除到它们的上限,从而使您的 match 语句仅在最一般的情况下与Any 匹配,即,所有内容都将使用putA 调用。如果它们不是通用的并且不是彼此的子类型,那么 put(o: Any) 仍然不是类型安全的,因为您可以放置​​ D,这将导致匹配错误。 - 关于你的问题:你可以用一个简单的元组来支持它。由于它将被接口抽象出来,因此您应该能够随时更换实现。
  • @KuluLimpa - 你可以通过ClassTagging 来做通用版本,然后使用isAssignableFrom。但是,如果类相同,这并不能阻止所有 B 进入 A 的插槽。
  • @RexKerr 谢谢你分享这个。我不知道ClassTag 反射功能。

标签: scala collections shapeless heterogeneous


【解决方案1】:

正如其他人所建议的,您可以利用 shapeless 的 Coproduct 类型。这是一个例子。

// let's define a Coproduct of the two types you want to support
type IS = Int :+: String :+: CNil

// now let's have a few instances
val i = Coproduct[IS](42)
val i2 = Coproduct[IS](43)
val s = Coproduct[IS]("foo")
val s2 = Coproduct[IS]("bar")

// let's put them in a container
val cont = List(i, s, i2, s2)

// now, do you want all the ints?
val ints = cont.map(_.select[Int]).flatten

// or all the strings?
val strings = cont.map(_.select[String]).flatten

// and of course you can add elements (it's a List)
val cont2 = Coproduct[IS](12) :: cont
val cont3 = Coproduct[IS]("baz") :: cont2

现在这当然不是通用容器最直观的 API,但可以使用 Coproduct 表示多种类型轻松地将逻辑封装在自定义类中。

这是一个实现的草图

import shapeless._; import ops.coproduct._

class Container[T <: Coproduct] private (underlying: List[T]) {
  def ::[A](a: A)(implicit ev: Inject[T, A]) =
    new Container(Coproduct[T](a) :: underlying)

  def get[A](implicit ev: Selector[T, A]) =
    underlying.map(_.select[A]).flatten

  override def toString = underlying.toString
}

object Container {
  def empty[T <: Coproduct] = new Container(List[T]())
}

例子

scala> type IS = Int :+: String :+: CNil
defined type alias IS

scala> val cont = 42 :: "foo" :: "bar" :: 43 :: Container.empty[IS]
cont: Container[IS] = List(42, foo, bar, 43)

scala> cont.get[Int]
res0: List[Int] = List(42, 43)

scala> cont.get[String]
res1: List[String] = List(foo, bar)

【讨论】:

    【解决方案2】:

    Miles Sabin 在unboxed union types 上发帖;这在他的shapeless 库中实现为CoProduct

    shapeless 有一个 Coproduct 类型,是 Scala 的 Either 对任意数量选择的推广

    我绝对不是 shapeless 方面的专家,但如果您使用 shapeless 标签创建新问题或编辑您的问题,那么您可以获得使用 CoProduct 所需的任何帮助

    【讨论】:

      【解决方案3】:

      你应该看看 Shapeless 的 HList 或 Coproduct;我不会自己重新发明这个。

      【讨论】:

        【解决方案4】:

        这是第一个版本,但我想对类型进行抽象:

        trait Container {
        
          def putInt(i: Int)
          def putString(s: String)
          def put(o: Any) = o match {
            case i: Int => putInt(i)
            case s: String => putString(s)
          }
          def getInts() : Seq[Int]
          def getStrings() : Seq[String]
        
        }
        
        class MutableContainer extends Container {
        
          val ints = mutable.ArrayBuffer[Int]()
          val strings = mutable.ArrayBuffer[String]()
        
          override def putInt(i: Int): Unit = ints += i
        
          override def putString(s: String): Unit = strings += s
        
          override def getStrings(): Seq[String] = strings
        
          override def getInts(): Seq[Int] = ints
        
        }
        
        object TestContainer extends App {
          val mc = new MutableContainer()
          mc.put("a")
          mc.put("b")
          mc.put(1)
          println(mc.getInts())
          println(mc.getStrings())
        }
        

        现在尝试抽象类型

        trait Container {
          def getInts() : Seq[Int]
          def getStrings() : Seq[String]
        
          def put[T](t: T)
          //def get[T] : Seq[T]
        }
        
        class MutableContainer extends Container {
          val entities = new mutable.HashMap[Class[_], mutable.Set[Any]]() with mutable.MultiMap[Class[_], Any]
        
          override def getStrings(): Seq[String] = entities.get(classOf[String]).map(_.toSeq).getOrElse(Seq.empty).asInstanceOf[Seq[String]] //strings
          override def getInts(): Seq[Int] = entities.get(classOf[Int]).map(_.toSeq).getOrElse(Seq.empty).asInstanceOf[Seq[Int]]
        
          //override def get[T]: Seq[T] = entities.get(classOf[T]).map(_.toSeq).getOrElse(Seq.empty).asInstanceOf[Seq[T]]
          override def put[T](t: T): Unit = entities.addBinding(t.getClass, t)
        }
        
        trait Containable[T] {
          def typ : String
        }
        
        trait Cont {
          implicit object IntContainable extends Containable[Int] {
            override def typ: String = "Int"
          }
          implicit object StringContainable extends Containable[String] {
            override def typ: String = "String"
          }
        }
        
        object TestContainer extends App {
          val mc = new MutableContainer()
          mc.put("a")
          mc.put("b")
          mc.put(1)
          println(mc.getInts())
          println(mc.getStrings())
          println(mc.entities.keys)
        }
        

        但是我遇到了 java.lang.Integer 和 Int 的问题……

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-07-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-10
          • 2010-09-16
          • 1970-01-01
          相关资源
          最近更新 更多