Как я могу параметризовать класс с типом объекта, чтобы получить его экземпляр в Scala?

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

У меня есть следующий код, который демонстрирует мою цель:

abstract trait Foo {
  def greet
}

object Coo extends Foo {
  def greet = println("Coo!")
}

object Boo extends Foo {
  def greet = println("Boo!")
}

class A[Fooer <: Foo] {
  def moo = asInstanceOf[Fooer].greet()
}

object Main extends App {
  new A[Coo.type].moo() // hopefully prints "Coo!"
}

Код вызывает исключение:

java.lang.ClassCastException: A cannot be cast to Foo

И я верю, что это из-за asInstanceOf вызов, который, по-видимому, использует this неявно.

По сути, мой вопрос заключается в следующем: как я могу получить экземпляр моего объекта по типу в классе с помощью параметризации?

1 ответ

Решение

И я верю, что это из-за asInstanceOf вызов, который, по-видимому, использует this неявно.

Каждый класс в Scala расширяется Any, который наследует специальный метод asInstanceOfкроме всего прочего. Я бы не сказал, что это происходит неявно- просто вы вызываете метод-член этого класса, который вызывает его сам по себе. Так же, как если бы вы написали def moo = greet,

Кроме того, в любое время вы используете asInstanceOfВы просите, чтобы компилятор лгал самому себе и продолжал. Это чаще всего приводит к ClassCastException, В этом случае это потому, что вы пытаетесь разыграть A в какой-то неизвестный подтип Foo, Это никогда не сработает. В очень конкретном случае вы пытаетесь передать одноэлементный тип Coo.type к примеру A а затем создать новый экземпляр Coo.type, Хотя существует множество других причин, по которым это не сработает, одна из них заключается в том, что вы не можете создать второй экземпляр одноэлементного типа - это называется по определенной причине.

В более общем смысле, вы не знаете, что вы можете просто создать объект заданного типа. Тип не имеет конструктора, а класс имеет.

Это может быть побочным эффектом упрощенного примера, но вы заинтересованы в создании экземпляра A с некоторым параметром типа без необходимости передавать ему фактический объект, но объекты, которые вас интересуют, уже существуют. Так почему бы не передать их?

class A[Fooer <: Foo](f: Fooer) {
  def moo = f.greet
}

Единственный способ сделать это - предоставить доказательства того, что Fooer может быть построен. Однако нет никаких ограничений типа для доказательства того, что что-то является конструируемым классом. Что вы можете сделать, это потребовать Class мета-объект вместо:

class A[Fooer <: Foo](clazz: Class[Fooer]) {
  def moo = clazz.newInstance.greet
}

Это грубо, однако. Во-первых, он не работает с вашими синглтон-типами выше (потому что они не могут быть созданы заново). А во-вторых, мы можем только позвонить newInstance когда нет параметров конструктора для предоставления, и нет способа обеспечить это.

Так что пока это будет работать

scala> class Bar extends Foo { def greet = print("Bar") }

scala> new A(classOf[Bar]).moo
Bar

Следующего не будет:

scala> class Baz(i: Int) extends Foo { def greet = println("Baz") }
defined class Baz

scala> new A(classOf[Baz]).moo
java.lang.InstantiationException: Baz

scala> new A(classOf[Coo.type]).moo
<console>:14: error: class type required but Coo.type found
       new A(classOf[Coo.type]).moo
                        ^

В качестве альтернативы вы можете использовать класс типов, который позволяет вам последовательно создавать экземпляры Foo, но это потребовало бы создания одного для каждого подкласса. например:

trait FooBuilder[A] {
    def build(): A
}

implicit object BarBuilder extends FooBuilder[Bar] {
    def build() = new Bar
}

class A[Fooer <: Foo](implicit fb: FooBuilder[Fooer]) {
    def moo = fb.build().greet
}
Другие вопросы по тегам