Проблема с общими коллекциями в Scala - PullRequest
3 голосов
/ 15 июня 2011

Я озадачен, как решить эту проблему в Scala.В Java я бы отказался от дженериков или использовал бы приведение, но Scala более строг, чем это.Вот что у меня есть.

  1. Абстрактный базовый класс Factory[+F <: Factory[F]] и некоторые конкретные реализации, не относящиеся к делу.Это ковариантно.У этого класса есть метод, который создает продукты - см. Следующий пункт.
  2. Абстрактный базовый класс Product[+F <: Factory[F]], представляющий продукт фабрики.Они также ковариантны и доступны только для чтения.Любая конкретная фабрика производит ровно один тип продукта (то есть не существует нескольких различных подтипов - FooFactory создает FooProduct, BarFactory создает BarProduct и т. Д.). Очевидно говоря здесь).В настоящее время это Iterable[Factory[_]].Это первая часть неприятностей.Похоже, что Scala здесь понимает _ как Any, игнорируя ограничение F <: Factory[F] для самого типа.
  3. У меня есть метод агрегированной фабрики, который просит каждую фабрику производить свой продукт с такими же аргументами.Результатом является Карта фабрик к их продуктам, в настоящее время закодированная как Map[Factory[_],Product[_]], а также опробованная Map[Factory[_],Product[Factory[_]]].Это еще одна часть, где я попал в беду.Первый _ не связан со вторым _, и оба, похоже, подразумеваются как Any.

Что я не могу сделать, так это создать эту карту.Это может быть или не важно, но я делаю это (и должен это делать), используя еще один метод, похожий на синтаксическую конструкцию.Это происходит из черты и:

  1. Вызывает объявленный метод (здесь он называется gather[B](fun: A => B): Map[A,B], не реализован в самой черте и не типизирован), который получает коллекцию фабрик.Фактический тип фабрики является общим и неизвестным.
  2. Перебирает эту универсальную коллекцию и выполняет переданную ей функцию.Эта функция на самом деле вызывает фабричный метод.
  3. Возвращает неизменное Map из F в P

Проблема компилятора не в методе "собирать", а в коде, которыйназывает этоЯ не могу привести его результат к Map[Factory[_],Product[_]], потому что _ не соответствует F <: Factory[F] ни в одном месте ... Я был бы более чем рад использовать базовый тип для _, но это было бы Factory[Factory[Factory[Factory......(infinitely many times)]]]]]].... и я не знаю, что делать.

Пожалуйста, помогите!

Ученик

Ответы [ 4 ]

5 голосов
/ 15 июня 2011

В большинстве случаев использование _ в типах - это неправильно, если вы не знаете, что делаете. И если вы не понимаете экзистенциальные типы, то вы наверняка не знаете, что делаете. Дело не в том, что Скала думает, что _ является Any, а в том, что знает, _ может означать что угодно - будь то Any, Nothing или что-то среднее. *

Подводя итог, Iterable[Factory[_]] означает Iterable[Factory[T]] forSome { type T }. Вы можете написать это явно как Iterable[Factory[T]] forSome { type T <: Factory[T] }, чтобы получить необходимые ограничения, так же, как вы можете написать это как Map[Factory[T], Product[T]] forSome { type T <: Factory[T] }.

Я не уверен, хотя, действительно ли это то, что вам нужно. Ваши вопросы направлены на то, как вы пытаетесь решить проблему, а не на то, как решить проблему. Например, может быть, что вместо обобщений будет лучше использовать следующее:

abstract class Factory {
    type T <: Factory
}
2 голосов
/ 15 июня 2011

Простите меня, если мой Скала немного ржавый.Я пишу в общем смысле ООП-и-дженериков.

Не ясно, почему Factory или Product вообще должны быть дженериками, много путаницы с ковариацией.Независимо от того, нужен он или нет, необходимо и достаточно иметь простой неуниверсальный класс FactoryBase, который в конечном итоге наследуют все Factory классы.Вы вставляете это в Iterable[FactoryBase].

. Также не ясно, почему тип Product содержит какую-либо информацию о Factory.Противоположность будет иметь больше смысла, если вам действительно нужна эта информация.Factory производит Product, следовательно, знает об этом, поэтому это знание может быть отражено в типе.Опять же, это может быть не так.

На всякий случай, если вам нужна универсальность, ковариация и информация о полном типе, вы можете использовать такие иерархии:

FactoryBase
   Factory[+F <: Factory[F,P], +P <: Product[F,P]] <: FactoryBase
      FooFactory <: Factory[FooFactory, FooProduct]
ProductBase
   Product[+F <: Factory[F,P], +P <: Product[F,P]] <: ProductBase
      FooProduct <: Product[FooFactory, FooProduct]

Не стесняйтесь опускать любые параметры типа, которые вы используетене нужно.

Также не ясно, зачем вам карта, которая отображает фабрики на продукты.Карты обычно отображают такие вещи, как имена для сущностей.То есть по имени найдите существующую сущность с таким именем.Фабрики создают новых из воздуха.В любом случае, если вам действительно нужна такая карта, вы можете использовать Map[FactoryBase, ProductBase].Это не дает статической гарантии того, что FooFactory отображается на FooProduct, а не на BarProduct, но Map не может этого обеспечить.Опять же, вы можете просто вставить Product внутрь соответствующего Factory.Вы уже знаете отображение во время компиляции, нет необходимости во время выполнения Map.С одной стороны, n, продукт не должен ассоциироваться с какой-либо фабрикой после того, как он произведен.

Возможно, я полностью вас неправильно понимаю, и вам нужно много проработать над своим дизайном.Как таковое, его назначение и архитектура совершенно не ясны.

1 голос
/ 15 июня 2011

Добавление другого ответа вместо редактирования старого.

Я буду говорить не обязательно с точки зрения системы типов Scala, поскольку идеи здесь применимы ко многим различным языкам.

Во-первых, прежде чем говорить о картах, позвольте мне показать, как создавать более глубокие параллельные иерархии. Это действительно легко, просто вставьте другой уровень:

FactoryBase
   Factory[+F <: Factory[F,P], +P <: Product[F,P]] <: FactoryBase
      SomeFactoryGroup[+F <: SomeFactoryGroup[F,P], +P <: SomeProductGroup[F,P]] <: Factory[SomeFactoryGroup, SomeProductGroup]
        FooFactory <: SomeFactoryGroup[FooFactory, FooProduct]
ProductBase
   Product[+F <: Factory[F,P], +P <: Product[F,P]] <: ProductBase
      SomeFactoryGroup[+F <: SomeFactoryGroup[F,P], +P <: SomeProductGroup[F,P]] <: Product[SomeFactoryGroup, SomeProductGroup]
        FooProduct <: SomeProductGroup[FooFactory, FooProduct]

Вы можете вставить столько уровней, сколько захотите. Нам нужны все из них, включая неуниверсальные FactoryBase и ProductBase. Хотя они не являются строго необходимыми, поскольку возможно избежать неприятностей с экзистенциальными типами, они могут стать громоздкими. Проще сказать FactoryBase, чем (SomeFactory | exist F <: Factory[F,P], exist P <: Product[F,P], SomeFactory <: F) (это намеренно не синтаксис Scala). Иногда экзистенциалы очень полезны, но не в этом примере (возможно, вы можете использовать их в других частях вашей системы). В любом случае, эти FactoryBase объекты будут помещены в Iterable[FactoryBase] для управления производственными прогонами.

Теперь к картам. Существуют производственные циклы, во время которых каждый завод может произвести один экземпляр продукта (или, возможно, ни одного). Нам нужно, учитывая завод и производственный цикл, найти продукт, произведенный этим заводом во время этого цикла.

Одним из решений является то, что вы пытались: представить производственные прогоны в виде (или содержать, или что-то еще) карт от заводов к продуктам. На мгновение игнорируем типы в псевдокоде:

productionrun.lookup(factory) = productionrun.mapFromFactoryToProduct.lookup(factory)

Это может даже сработать, если мы будем придерживаться FactoryBase и ProductBase. Но этот подход ограничивает. Карта отображает группу вещей типа A на группу вещей (возможно, разных) типа B. Все ключи идентичны по типу, а все значения идентичны по типу. Это явно не то, что мы имеем. Все фабрики разных типов, как и все продукты. Мы можем обойти эту проблему, забыв часть информации о типе, то есть, сохранив ключи и значения, приведенные к их типу наименьшего общего знаменателя. Но что, если нам понадобится восстановить эту информацию типа позже? Это невозможно сделать статически, это забыто, потеряно навсегда.

Другое решение внешне симметрично первому: фабрики представляют (или содержат, в данном случае) карты от производственных циклов до продуктов.

factory.lookup(productionrun) = factory.mapFromProductionRunToProduct.lookup(productionrun)

Производственные прогоны в этом случае представлены только их уникальными идентификаторами. Но на уровне типов это решение сильно отличается и намного лучше, чем первое. Все ключи на каждой карте идентичны по типу (фактически все они идентичны по типу на разных фабриках). Все значения в каждой карте идентичны типу (этот тип зависит от фабрики). На любом этапе информация о типе не теряется.

Итак, подведем итоги. Имейте класс PR, который представляет производственный цикл. Создайте метод Factory[F,P].findProduct(pr:PR)->Product[F,P], реализованный с помощью Map[PR, Product[F,P]]. Пусть метод makeProduct примет значение PR и добавит полученный продукт на карту, указав это значение PR. Вы можете обернуть это в метод PR.lookUpProductByFactory[F,P](f:Factory[F,P])->Product[F,P] или что-то подобное, если хотите. Кроме того, оберните это в FactoryBase.findProduct(pr:PR)->ProductBase, и оберните , что в PR.lookUpProductBaseByFactoryBase(f:FactoryBase)->ProductBase.

Вот и все. Я надеюсь, что эти два решения подойдут вам.

1 голос
/ 15 июня 2011

Я не совсем уверен, что понимаю вашу проблему, но попробуйте использовать что-то вроде:

gather[A : Factory, B : Factory](fun: A => B): Map[A,B]
...