Scala: как использовать типы в качестве первоклассных значений внутри конструкторов класса case? - PullRequest
0 голосов
/ 31 августа 2018

Предположим, у меня есть несколько автоматически сгенерированных классов, таких как MyEnum1, MyEnum2, ... (они не обязательно являются типами перечисления Scala, только некоторые автоматически сгенерированные классы). Хотя тип MyEnum1 отличается от типа MyEnum2 (и они не имеют автоматически сгенерированных родительских типов, кроме Any), я могу гарантировать, что все эти автоматически сгенерированные типы имеют абсолютно одинаковые public, static доступные методы, в частности findById и findByName, которые позволяют искать значение перечисления на основе индекса или имени строки.

Я пытаюсь создать функцию, которая будет использовать специфичную для типа версию findById и findByName, но в общем случае принимает любой из MyEnum1, MyEnum2, ... в качестве параметра функции.

Обратите внимание, что типичный шаблон sealed trait + case class для создания типа суммы из различных перечислений здесь не поможет, потому что я говорю о распределении различных статических методов, основанных на параметре типа, и никогда не существует параметр фактического значения задействован вообще.

Например, предположим, что MyEnum1 кодирует мужской / женский пол. Так что MyEnum1.findById(0) возвращает MyEnum1.Female, который имеет тип MyEnum1. И скажем, MyEnum2 кодирует цвет глаз, так что MyEnum2.findById(0) возвращает MyEnum2.Green, который имеет тип MyEnum2.

Мне дана Карта, ключом которой является тип , а значением является индекс для поиска, например

val typeMap = Map(
  MyEnum1 -> 0,
  MyEnum2 -> 0
)

и я хотел бы сделать это в общем:

for ( (elemType, idx) <- typeMap ) yield elemType.findById(v)
                                         |---------------|
                                          the goal is to
                                          avoid boilerplate
                                          of defining this
                                          with different
                                          pattern matching
                                          for every enum.

и получить некоторый тип последовательности (может иметь тип элемента Any), который выглядит как

MyEnum1.Female, MyEnum2.Green, ...

Некоторое время я боролся с шаблоном sealed trait + case class, и, похоже, концептуально это не правильный путь. Неважно, если я оберну значения из MyEnum1 или MyEnum2 в конструкторы значений класса дела, такие как FromMyEnum1(e: MyEnum1), и попытаюсь определить последствия для работы с этим значением , это не помогите в моем примере кода выше, когда я хочу сделать elemType.findById(...), потому что компилятор все еще говорит, что тип Any (что он разрешает для типа ключа в моем Map), не имеет метода findById.

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

case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait

(так, чтобы ключи Map могли иметь тип EnumTrait и, возможно, могло быть какое-то неявное выражение, которое соответствовало бы каждому конструктору класса дел с правильной реализацией findById или findByName).

Любая помощь в понимании того, как Scala позволяет использовать сами типы в качестве значений внутри конструкторов значений класса case, приветствуется!

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Если у класса есть фактический экземпляр (количество объектов-одиночек), вы можете использовать структурные типы:

type Enum[A] = {
  def findById(id: Int): E
  def findByName(name: String): E
  def values(): Array[E]
}

trait SomeEnum
object SomeEnum {
  case object Value1 extends SomeEnum
  case object Value2 extends SomeEnum

  def findById(id: Int): SomeEnum = ???
  def findByName(name: String): SomeEnum = ???
  def values(): Array[SomeEnum] = ???
}

trait SomeEnum2
object SomeEnum2 {
  case object Value1 extends SomeEnum2
  case object Value2 extends SomeEnum2

  def findById(id: Int): SomeEnum2 = ???
  def findByName(name: String): SomeEnum2 = ???
  def values(): Array[SomeEnum2] = ???
}

val x: Enum[SomeEnum] = SomeEnum
val y: Enum[SomeEnum2] = SomeEnum2

Так что, если вы работаете только со Scala, все просто.

Но у классов Java нет сопутствующих объектов - вы получите object mypackage.MyEnum is not a value. Это не будет работать. Для этого вам придется использовать рефлексию, поэтому у вас возникнет проблема с сохранением согласованности API для всех случаев.

Однако то, что вы могли бы сделать, было бы примерно так:

  1. определяет общий набор операций, например

    trait Enum[A] {
    
      def findById(id: Int): A = ???
      def findByName(name: String): A = ???
      def values(): Array[A] = ???
    }
    
  2. обрабатывать каждый случай разделения:

    def buildJavaEnumInstance[E <: java.util.Enum: ClassTag]: Enum[E] = new Enum[E] {
      // use reflection here to implement methods
      // you dont
    }
    
    def buildCoproductEnum = // use shapeless or macros to get all known instances
    // https://stackoverflow.com/questions/12078366/can-i-get-a-compile-time-list-of-all-of-the-case-objects-which-derive-from-a-sea
    
    ...
    
  3. создайте объект-компаньон и обработайте эти случаи со следствиями:

    object Enum {
    
      def apply[E](implicit e: Enum[E]): Enum[E] = e
      implicit def buildJavaEnumInstance[E <: java.util.Enum: ClassTag] = ???
      implicit def buildCoproductEnum = ???
      ...
    }
    
  4. Используйте Enum как класс типов или что-то в этом роде.

    def iNeedSomeEnumHere[E: Enum](param: String): E =
      Enum[E].findByName(param)
    

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

0 голосов
/ 31 августа 2018

В вашем вопросе есть некоторые фундаментальные заблуждения.

Во-первых, в Scala нет «статических методов», все методы привязаны к экземпляру класса. Если вы хотите, чтобы метод был одинаковым для каждого экземпляра класса, вы добавляете метод в объект-компаньон для этого класса и вызываете его для этого объекта.

Во-вторых, вы не можете вызывать метод для типа, вы можете вызывать метод только для экземпляра типа. Поэтому вы не можете вызвать findById для одного из MyEnum типов, вы можете вызвать его только для экземпляра одного из этих типов.

В-третьих, вы не можете вернуть тип из метода, вы можете только вернуть экземпляр типа.


Трудно точно сказать, чего вы пытаетесь достичь, но я подозреваю, что MyEnum1, MyEnum2 должны быть объектами, а не классами. Они наследуются от общего интерфейса, который вы определили (findById, findByName). Затем вы можете создать Map из экземпляра общего типа в индекс для использования в вызове findById.


Пример кода:

trait MyEnum {
  def findById(id: Int): Any
  def findByName(name: String): Any
}

object MyEnum1 extends MyEnum {
  trait Gender
  object Male extends Gender
  object Female extends Gender

  def findById(id: Int): Gender = Male
  def findByName(name: String): Gender = Female
}

object MyEnum2 extends MyEnum {
  trait Colour
  object Red extends Colour
  object Green extends Colour
  object Blue extends Colour

  def findById(id: Int): Colour = Red
  def findByName(name: String): Colour = Blue
}

val typeMap = Map(
  MyEnum1 -> 0,
  MyEnum2 -> 0,
)


for ((elemType, idx) <- typeMap ) yield elemType.findById(idx)

Если вы не можете предоставить общего родителя trait, используйте структурный тип:

object MyEnum1 {
  trait Gender
  object Male extends Gender
  object Female extends Gender

  def findById(id: Int): Gender = Male
  def findByName(name: String): Gender = Female
}

object MyEnum2 {
  trait Colour
  object Red extends Colour
  object Green extends Colour
  object Blue extends Colour

  def findById(id: Int): Colour = Red
  def findByName(name: String): Colour = Blue
}

type MyEnum = {
  def findById(id: Int): Any
  def findByName(name: String): Any
}

val typeMap = Map[MyEnum, Int](
  MyEnum1 -> 0,
  MyEnum2 -> 0,
)

for ((elemType, idx) <- typeMap) yield elemType.findById(idx)
...