Узнайте, наследуются ли две переменные одного и того же параметризованного типа в Scala - PullRequest
3 голосов
/ 03 декабря 2011

вот моя проблема:

Я пытаюсь собрать список объектов:

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)

и агрегируйте их, если они являются объединяемой парой:

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 ]

1 голос
/ 10 декабря 2011

Я нашел довольно «удовлетворительное» решение моей проблемы. Общая идея состоит в том, чтобы добавить метод aggregateOrCons с типом результата List [Any] к признаку 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 (который приведет либо к агрегации двух аргументов, если они равны, либо к списку, содержащему аргументы, если они не совпадают). Если один из них не является Агрегируемым, будет возвращен список, содержащий входные аргументы.

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))
0 голосов
/ 05 декабря 2011

Параметр типа T не может быть определен во время выполнения из-за стирания типа JVM. Но вам не нужно (и вы знаете, что такое T, потому что это также класс из getClass ключей на карте, но вам не нужно знать).

Поскольку вы знаете, что a и b относятся к одному и тому же типу (например, Foo), то если a.instanceOf[Aggregatable] (или a.instanceOf[Aggregatable[_]]), то b.instanceOf[Aggregatable] тоже. Поэтому просто протестируйте первый элемент списка и, если оно истинно, соберите все элементы.


P.S. Я бы использовал библиотеку коллекции теорий *1015*, которая имеет тип Monoid, вместо того, чтобы изобретать ее как Aggregatable. Monoid знает, как накапливать себя. См. Стр. 7 из Прикладное программирование с эффектами .

Концептуально вы выполняете следующие функциональные программы шагов.

  1. Сверните список, создав коллекцию (например, список или карту) списков, сопоставляя элементы одного типа в разных списках. Используйте elem.getClass для проверки на различимость.
  2. Карта коллекции списков, сворачивание каждого подсписка для накопления.

Я вижу, вы достигли # 1 с groupBy.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...