Понимание ковариации в моем коде Scala - PullRequest
0 голосов
/ 11 января 2019

Я работаю с правильным синтаксисом и структурой для следующей проблемы.

У меня есть два набора данных с двумя отдельными схемами - назовите их ClientEvent и ServerEvent - хранящихся на диске. Кодовая база, над которой я работаю, определила класс, Reader[T :< Asset], где ClientEvent и ServerEvent - это подтипы Asset. Asset это черта.

Я пишу функцию:

def getPathAndReader(config): (String, Reader[Asset]) = {
    if (config.readClient) {
        return getClientPathAndReader(config)
    } else {
        return getServerPathAndReader(config)
    } 
}

Это не компилируется в моем коде Scala. Насколько я понимаю, T должен быть подтипом Asset, который как ServerEvent, так и ClientEvent, следовательно, Reader[ServerEvent] <: Reader[Asset]. Но поскольку функции являются ковариантными в своих входных данных, функция, которую я написал, не может просто возвращать этот более низкий тип, я должен был бы привести его к супертипу? Это теряет слишком много информации?

load - функция на признаке Asset

trait Reader[T <: Asset] {
  def load(raw: DataFrame): Dataset[T]
}

Каким был бы альтернативный способ структурировать этот код?

Цель кода - взять возвращенный путь к файлу и вызвать Reader::load(filePath: String), чтобы получить данные обратно. Читатели с подтипами имеют некоторую внутреннюю логику для очистки данных, которые он извлекает с диска, прежде чем они будут возвращены как Dataframe. Это означает, что он опирается на тип, который он передает. Я пришел из C ++ / C # фона, поэтому я думаю, что если у вас есть общий Reader[Asset], но вызовите Reader::load(path: String), он будет знать, что делать, основываясь на типе, который он на самом деле аналогично Base* ptr и вызывает производный метод.

1 Ответ

0 голосов
/ 11 января 2019

Вы утверждаете, что "Насколько я понимаю, T должен быть подтипом Asset, который как ServerEvent, так и ClientEvent, следовательно, Reader[ServerEvent] <: Reader[Asset]." не является правильным. Обычно, если A и B являются обычными типами, такими как A <: B, а G[T] является универсальным типом, тогда возможны все 3 случая:

  • Совместный вариант G[A] <: G[B] - типичным примером является некоторая коллекция только для чтения, такая как Iterator
  • Контра-вариант G[A] :> G[B] - типичным примером является потребитель, подобный функции T => ()
  • Инвариантный случай, когда G[A] и G[B] не связаны. Наиболее типичный случай, когда некоторые применения T являются ко-вариантами, а некоторые - контравариантными. Например, простая функция отображения T => T является инвариантной. Кроме того, большинство изменяемых коллекций также инвариантны, поскольку объекты «производят» и «потребляют».

К сожалению для вас Dataset[T] является инвариантом (а не ковариантным Dataset[+T] или контравариантным Dataset[-T]). Это эффективно делает ваш Reader также инвариантным. Что касается того, как обойти это, трудно дать совет без понимания более широкого контекста. Например, почему ваши getClientPathAndReader и getServerPathAndReader не возвращают Dataset[Asset]? Если вы действительно тогда используете определенные ServerEvent и ClientEvent, то ваш дизайн в любом случае небезопасен. Если вы используете только Asset, то замена читателей на Dataset[Asset] кажется наиболее простым решением.

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