Наследование Скалы; Проблема строителя; Неуниверсальный IterableLike
Я пытаюсь реализовать простую цель проектирования, но сложность системы типов Scala вызывает у меня некоторую головную боль. После сравнения Traversable, Iterator, Iterable, Stream, View и т. Д. Я решил определить пользовательскую черту (давайте просто назовем ее Stream
для краткости) что
- не является универсальным (мой поток семантически имеет смысл только как некоторые
Stream[StreamEntry]
и я хочу избежать бессмысленных типов, таких какStream[Int]
) - имеет аналогичное использование
Iterable
- все участники любят
take
,drop
и т. д. должен вернутьсяStream
а не основнойIterable
,
Это то, что я пробовал до сих пор:
Подход 1
Чтобы сделать набросок сценария использования, простым примером (который нарушает третью цель разработки) будет:
case class StreamEntry(data: Double) // just a dummy
trait Stream extends Iterable[StreamEntry] {
val metaInfo: String
}
// example use case
val s = new Stream {
val metaInfo = "something"
val iterator = StreamEntry(1) :: StreamEntry(2) :: StreamEntry(3) :: Nil toIterator
}
val t = s.take(1) // unfortunately, this is no longer a Stream
Подход 2
Это третье требование требует использования шаблонной черты вместо базовой (я надеюсь, что это стандартная терминология для обозначения SomeCollection или SomeCollectionLike). Это означает, что я должен использовать IterableLike[StreamEntry, Stream]
который переопределяет типы возврата представляющей коллекции так же, как Iterable
продолжается IterableLike[A, Iterable[A]]
возвращать Iterable
s. Моя идея заключалась в том, чтобы сделать почти так же, как Iterable
делает. Это было бы:
// this is exactly the way `Iterable` is defined, but non-generic
trait Stream extends Traversable[StreamEntry]
with GenIterable[StreamEntry]
with GenericTraversableTemplate[StreamEntry, Stream]
with IterableLike[StreamEntry, Stream] {
...
}
К сожалению, это не компилируется, потому что Stream
появляется в качестве аргумента шаблона для GenericTraversableTemplate
и компилятор теперь требует аргумент шаблона (ровно один) для Stream
сам, что имеет смысл.
Подход 3, 4, ...
Отсюда я заблудился в системе типов. Просто удаляя with GenericTraversableTemplate
приводит к несовместимому типу newBuilder
и незаконное наследование из-за конфликтов в параметрах типа в GenericTraversableTemplate
от GenInterable
а также Traversable
,
Возможно, самым близким решением было следующее:
trait Stream extends TraversableLike[StreamEntry, Stream]
with IterableLike[StreamEntry, Stream] {
val metaInfo: String
def seq = this
def newBuilder: scala.collection.mutable.Builder[StreamEntry, Stream] = ???
}
Это компилируется, но, к сожалению, я понятия не имею, как реализовать Builder. Можно ли повторно использовать универсальный Builder для моей неуниверсальной черты? На самом деле я, хотя я могу обойтись без Строителя, потому что я никогда не хочу строить новый Stream
из других коллекций. Но в настоящее время я испытываю некоторое странное поведение во время выполнения с этим подходом, который я не могу полностью понять. Например:
val s = new Stream {
val metaInfo = "something"
val iterator = StreamEntry(1) :: StreamEntry(2) :: StreamEntry(3) :: Nil toIterator
}
// executing the following independently (not in sequence) results in:
s.take(1) // throws: scala.NotImplementedError: an implementation is missing
// seems to require a Builder :(
s.toArray // works
s.toIterator // works
s.toIterable // throws: java.lang.ClassCastException: cannot be cast to scala.collection.Iterable
Теперь я чувствую себя немного потерянным в глубине системы типов Scala. Я все еще на правильном пути с этим последним подходом, и Строитель - просто недостающая часть в этой загадке?
И как будет выглядеть реализация Builder для неуниверсального некэшируемого типа? Простая идея для реализации +=
было бы использовать некоторый изменяемый буфер, но это было бы очень против использования итераторов в первую очередь... И как я должен реализовать to
член, если я не знаю, как построить класс этого типа? Я думаю, что весь соответствующий код должен быть где-то в библиотеке, я просто не могу выкопать его.
1 ответ
Вот Это Да! У тебя там много чего происходит...
Вот некоторые вещи, которые вы должны знать или учитывать при решении этой проблемы проектирования...
Терминология:
- Мы не ссылаемся на "шаблоны", мы называем их "универсальными" или "параметризованными" типами. Причина этого в том, что эти типы не являются шаблонами! То есть они не заполнены фактическими параметрами типа для создания новых классов каждый раз, когда они используются (как в случае с C++, который правильно использует термин "шаблон"). Вместо этого создается только один класс (*), который обслуживает каждый экземпляр этого универсального типа с определенными параметрами типа.
Факторы дизайна и языка:
Ты говоришь:
... не является универсальным (мой поток семантически имеет смысл только как какой-то Stream[StreamEntry], и я хочу избежать бессмысленных типов, таких как Stream[Int])
Требование не распространяется на неуниверсальный класс. Скорее, это суть того, что такое "привязка типа". Например:
class Generic[T <: UpperBound](ctorArgs...) {
}
В этом случае класс Generic
могут быть созданы только с типами, которые являются подтипами UpperBound
, (Обратите внимание, что всякий раз, когда мы говорим "подтип", мы подразумеваем рефлексивное отношение подтипа. Другими словами, каждый тип является подтипом самого себя согласно этому определению.
Итог:
Интересно, что ваш "потоковый" класс является или делает или имеет, что не удовлетворяет существующий тип в стандартной библиотеке Scala? Как вы обнаружили, расширение стандартных библиотечных классов не совсем тривиально, хотя, безусловно, выполнимо. Я думаю, что это не элементарное упражнение в программировании Scala, и, вероятно, его не следует пытаться сделать одним из первых набегов в Scala.
(*) Это упрощение, которое достаточно для целей этого объяснения.