2017-01-24 2 views
4

[РЕДАКТИРОВАНИЕ НИЖЕ]Производные классы как конструктор класса параметров

У меня есть иерархия классов, то есть, прямо сейчас, примерно следующим образом:

sealed trait Foo { 
    def method: Any 
} 

case class Bar extends Foo { 
    def method: Array[String] = // implementation 
} 

case class Baz extends Foo { 
    def method: Map[String, Array[String]] = // implementation 
} 

Я даю абстрактный метод тип возврата Any потому что возвращаемые типы классов случаев обязательно разные, но они имеют сходную цель. По этой причине я хотел бы сохранить это в черте, чтобы моделировать это обычное поведение, и это единственный способ, которым я нашел его компиляцию. Я понимаю, что это противоречит духу системы типа Scala, поэтому я задаю первый вопрос ниже.

Затем еще один класс ожидает подкласс Foo в качестве параметра конструктора, и я не знаю, как обозначить это, кроме следующих:

class Qux(foo: Foo) { 
    val m = foo.method 
    ... 
    ... 
} 

Позже в классе Qux есть методы, которые ожидают Вали m быть типа, соответствующего конкретный подкласс (Bar или Baz) из Foo, но я получаю ошибку компиляции, как

... type mismatch; 
[error] found : Any 
[error] required: Array[String] 

Поэтому у меня есть несколько вопросов:

  • Я достаточно с Scala знаком поверить, что это правильный путь, чтобы представить свою конкретную проблему, но не достаточно с ним знаком, чтобы знать, как идти об этом. Каков правильный способ делать то, что я пытаюсь сделать?
  • Кроме того, есть ли способ сказать класс Qux что m следует рассматривать в качестве значения, возвращаемого определенной method из Bar или Baz, а не абстрактный метод из Foo?

Edit: Принимая подход, предложенный @marios (с использованием абстрактных членов типа), как представляется, является шагом в правильном направлении, но несоответствие типов выскакивает прямо сейчас. В классе Qux, теперь у меня есть

class Qux[X <: Foo](sc: SparkContext, val foo: X) { 
    val m: foo.A = foo.method 

    def process(rows: DataFrame) = foo match { 
    case Bar(sc, _) => BarProcessor(sc, m).doStuff(rows) 
    case Baz(sc, _) => BazProcessor(sc, m.keys).doStuff(rows, m.values) 
    } 
} 

Где BarProcessor инстанцируется, например, в Array[String] и BazProcessor нужны пары ключ-значение из значения, возвращаемого Baz «s method делать такие вещи. Тем не менее, сейчас я получаю сообщение об ошибке, как

[error] Qux.scala:4: type mismatch; 
[error] found : Qux.this.foo.A 
[error] required: Array[String] 
[error]  case Bar(sc, _) => BarProcessor(sc, m).doStuff(rows) 
[error]          ^

Подобные ошибки появляются, когда я пытаюсь вызвать Map Определённые методы на m когда foo является Baz (вдоль линий value keys is not a member of Qux.this.foo.A и т.д.). Я понимаю, что m не действительно a Array[String] - это тип A. Но есть ли способ сказать Скале «перевести» это в свой желаемый тип?

+1

«параметр типа», который может быть сдерживаются «граница». –

ответ

4

простой способ доступа к отдельным видам в вашем ADT является использование элементаабстрактного типа вместо родового параметра типа.

sealed trait Foo { 
    type A 
    def method: A 
} 

case object Bar extends Foo { 
    type A = Array[String] 
    def method: A = Array.empty[String] 
} 

case object Baz extends Foo { 
    type A = Map[String, Array[String]] 
    def method: A = Map.empty[String, Array[String]] 
} 

case class Qux[X <: Foo](foo: X) { 
    def m: X#A = foo.method 

    // You can then pattern match on m 
    def f = m match { 
    case a: Baz.A => a.size // Use Baz#A if Baz is a class and not an object 
    case b: Bar.A => b.size // Use Bar#A if Bar is a class and not an object 
    } 
} 

С его помощью (смотреть на типы возвращаемых)

@ Qux(Baz).m 
res6: Map[String, Array[String]] = Map() 

@ Qux(Bar).m 
res7: Array[String] = Array() 
+0

Это, кажется, очень перспективный подход; Спасибо! Тем не менее, я компилятору жалуюсь, что «тип A не является членом параметра типа X» в определении 'Qux'. – user4601931

+0

Хммм ... В какой версии вы используете? Я пробовал это по 2.11.8. Я просто дал ему еще один шанс. – marios

+0

Используйте ': paste' в своей оболочке, а затем скопируйте весь код. Это работает для вас? – marios

3

Вы можете добавить параметр типа к вашему признаку, как это:

sealed trait Foo[A] { 
    def method: A 
} 

case class Bar extends Foo[Array[String]] { 
    def method: Array[String] 
} 

case class Baz extends Foo[Map[String, Array[String]]] { 
    def method: Map[String, Array[String]] 
} 
+0

Это самый простой способ сделать это! Хороший ответ. Однако я не уверен, действительно ли это касается второй части вопроса. Как бы Qux работал? – marios

+0

Добавьте переменную класса класса в этот класс. Что-то вроде 'class Qux [A]', или, может быть, 'class Qux [_]', если вам не нужен тип – Tyler

+0

Но он действительно заботится о типе. 'val m = foo.method', OP хочет, чтобы m был таким же типом A' Foo [A] '. Я думаю, что-то вроде 'class Qux [A] (foo: Foo [A])' должно делать трюк. – marios

Смежные вопросы