Есть ли способ реализовать иерархическое перечисление в Scala? - PullRequest
0 голосов
/ 08 февраля 2019

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

Например, из source1 Я могу получить

Source1:

    {
     "message_type": "typeA",
     "value": 3 
     ...
    }

или

    {
     "message_type": "typeB",
     "value": 3 
     ...
    }

Но также от source2 Я могу получить

Source2:

   {
     "message_type": "A", 
     "value": 5 
     ...
   }

или

    {
     "message_type": "B",
     "value": 2 
     ...
    }

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

Первый созданный мною файл scala является признаком:

trait MessageType extends Enumeration {
    val TYPE_A: Value
    val TYPE_B: Value
}

, затем я реализовал его в двух объектных файлах:

object Source1MessageType extends MessageType{
    override val TYPE_A: Value("typeA")
    override val TYPE_B: Value("typeB")

object Source2MessageType extends MessageType{
    override val TYPE_A: Value("A")
    override val TYPE_B: Value("B")

Итак, теперь я хочупроверьте тип сообщения, не зная тип источника, например:

def foo(type: MessageType.Value) {
    type match{
        case MessageType.TYPE_A => ...do A action...
        case MessageType.TYPE_B => ...do B action...
    }
}

Но если я напишу этот код, IDE (IntelliJ) выделит параметр красным, но не даст информации об ошибке,Кажется, что я могу использовать только Source1MessageType или Source2MessageType в качестве типа параметра.

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

У вас есть какое-то решение для этого?

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

Если «A» и «typeA» являются именами для одного и того же типа сообщения, вам нужно иметь дело с этим, когда вы читаете данные.Это означает, что в вашем перечислении вам не нужна вложенность.

trait MessageType
case object MessageTypeA extends MessageType
case object MessageTypeB extends MessageType

object MessageType {
  def apply(s: String): MessageType =
    s match {
      case "A" | "typeA" => MessageTypeA
      case "B" | "typeB" => MessageTypeB
      case _ => throw BadMessageType
    }
}

case class Message(msgType: MessageType, value: Int)

Вы можете создать тип сообщения с помощью MessageType(<string>), и оно будет возвращать MessageTypeA или MessageTypeB в зависимости от ситуации.Вы можете использовать обычный match, чтобы определить, какой тип сообщения у вас есть.


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

trait MessageType {
  def origin: String
}

case class MessageTypeA(origin: String) extends MessageType
case class MessageTypeB(origin: String) extends MessageType

object MessageType {
  def apply(s: String): MessageType =
    s match {
      case "A" | "typeA" => MessageTypeA(s)
      case "B" | "typeB" => MessageTypeB(s)
      case _ => throw BadMessageType
    }
}
0 голосов
/ 08 февраля 2019

да, вы можете делать иерархические перечисления.Поэтому я бы вообще рекомендовал не использовать Enumeration.Вот статья о том, почему это плохо

https://medium.com/@yuriigorbylov/scala-enumerations-hell-5bdba2c1216

Самый идиоматичный способ сделать это - использовать запечатанные черты, подобные этому:

sealed trait MessageType{
  def value:String
}
sealed trait MessageType1 extends MessageType
final case object TypeA extends MessageType1{
   override def value:String = "typeA"
}
final case object TypeB extends MessageType1{
   override def value:String = "typeB"
}
sealed trait MessageType2 extends MessageType
final case object A extends MessageType2{
   override def value:String = "A"
}
final case object B extends MessageType2{
   override def value:String = "B"
}

Обратите внимание, что все этиопределения должны быть в одном файле.Теперь это работает, потому что sealed и final сообщают компилятору, что наследование может происходить только в этом файле.

Это означает, что при наличии экземпляра MessageType2 компилятор знает, что он может быть только объектомA или B это не может быть что-либо еще (из-за запечатанного / окончательного)

Это дает вам перечисления с исчерпывающими проверками в сопоставлении с образцом и т. Д.

...