找出两个变量是否从Scala中的相同参数化类型继承

时间:2011-12-03 12:26:58

标签: scala

这是我的问题:

我尝试聚合对象列表:

val list = List(Foo(1), Foo(2), Bar(2), Bar(3), Baz(5), Baz(3))

在聚合之后,我希望此列表中的每个可聚合类型只有一个对象。在这个例子中,Foo和Bar应该是可聚合的,而Baz不是,所以结果应该是:

List(Foo(3), Bar(5), Baz(5), Baz(3))

我的想法是定义一个特性聚合,如下所示:

trait Aggregatable[T] {
    def aggregate(agg: T): T
}

case class Foo(val x: Int) extends Aggregatable[Foo] {
    def aggregate(agg: Foo) = {
        val x = (0 /: List(this, agg))((old, elem) => (old + elem.x))
        new Foo(x)
    }
}

case class Bar(val x: Int) extends Aggregatable[Bar] {
    def aggregate(agg: Bar) = {
        val x = (0 /: List(this, agg))((old, elem) => (old + elem.x))
        new Bar(x)
    }   
}

case class Baz(val x: Int)

嗯,我认为这是问题的明显部分......

在下一步中,我尝试聚合列表。首先,我将列表分为同类型列表:

val grouped = list.groupBy( _.getClass().toString() )

/* => grouped should be
 * Map(
 *     class Foo -> 
 *         List(Foo(1), Foo(2)),
 *     class Bar -> 
 *         List(Bar(3), Bar(4)), 
 *     class Baz -> 
 *         List(Baz(5), Baz(3))
 * )
 */

现在为了简单起见,我们现在假设我们想要找出第一个列表的前两个元素是否可聚合:

val firstList = grouped.toList.apply(0)._2 // List(Foo(1), Foo(2))
val a = firstList (0) // Foo(1)
val b = firstList (1) // Foo(2)

这是我实际问题的开始。要确定a和b是否可以聚合,必须有一种方法可以询问a和b是否从相同类型的Aggregatable [T]继承某些固定的T.

我问这个问题的方法是定义一个类型aggregatablePair:

type aggregatablePair = Pair[T, T] forSome { type T <: Aggregatable[T] }

从a和b中构建一对:

val pair = (a, b)

并汇总它们,如果它们是一个aggregatablePair:

pair match {
    case aggPair: aggregatablePair => aggPair._1.aggregate(aggPair._2)
    case _ => println("pair is not aggregatable")
}

但这不起作用......错误是:

type mismatch; 
found: aggPair._2.type (with underlying type T forSome { type T <: Aggregatable[T] })
required: T where type T <: Aggregatable[T]

在我看来,这听起来像找到的类型匹配所需的类型...任何人都可以告诉我为什么它不? 什么是表达我想要的正确方法?

感谢您的帮助

2 个答案:

答案 0 :(得分:1)

我找到了一个令人满意的&#34;令人满意的&#34;解决我的问题。一般的想法是将一个结果类型为List [Any]的方法aggregateOrCons添加到Aggregatable特征中,如果它们来自同一类型,则聚合两个对象或返回包含输入参数的列表。

trait Aggregatable[T] {
    def aggregate(agg: T): T

    def aggregateOrCons(agg: Any): List[Any] = {
        agg match {
            case t: T => List(this.aggregate(t))
            case a => List(a, this)
        }
    }
}

我的输入参数现在正在排序,而不是按类分组,因为我只需要确保相同类型的对象出现在一行中。

val list = List(new Foo(1), new Baz(1), new Baz(2), new Bar(3), new Foo(2), new Bar(4))
val sorted = list.sortWith(
    (a1, a2) => (a1.getClass().toString() compareTo a2.getClass().toString()) < 0
)

在下一步中,我定义了一个方法来聚合两个Any类型的对象。如果两个输入参数都是Aggregatable类型,我对它们应用aggregatableOrCons方法(如果两个参数相等,则会产生两个参数的聚合,如果不相同则产生包含参数的列表)。如果其中一个不是Aggregatable,则将返回包含输入参数的列表。

def aggregate(a: Any, b: Any): List[Any] = a match {
    case agg1: Aggregatable[_] => b match {
        case agg2: Aggregatable[_] => agg1.aggregateOrCons(agg2)
        case b => List(b, agg1)
    }
    case a => List(b, a)
}

现在,能够折叠已排序的输入动画列表的唯一要求是中性元素。它应该与任何东西聚合,并且应该只返回输入参数。

object NeutralAggregatable extends Aggregatable[Any] {
    def aggregate(agg: Any) = agg
}

现在我可以折叠已排序的列表

val neutral: Any = NeutralAggregatable
val aggregated = (List(neutral) /: sorted)((old, elem) => 
    (aggregate(old.head, elem) ::: old.tail)
)

println(aggregated) // List(Foo(3), Baz(2), Baz(1), Bar(7))

答案 1 :(得分:0)

由于JVM的类型擦除,无法在运行时确定类型参数T。但你不需要(而且你知道T是什么,因为它也是地图中getClass个键的类,但你不需要知道。)

由于您知道ab属于同一类型(例如Foo),如果a.instanceOf[Aggregatable](或a.instanceOf[Aggregatable[_]]),则{{ 1}}也是。所以只需测试列表的第一项,如果为true,则累积所有元素。


P.S。我会使用具有b.instanceOf[Aggregatable]类型的category theory collection library,而不是将其重新发布为MonoidAggregatable知道如何积累自己。请参阅Applicative programming with effects的第7页。

从概念上讲,您正在执行以下functional programming步骤。

  1. 折叠列表,创建列表的集合(例如列表或地图),在不同的列表中整理相同类型的项目。使用Monoid来测试清晰度。
  2. 映射列表集合,折叠每个子列表以累积。
  3. 我看到你用elem.getClass完成了#1。