В Scala, каким должен быть тип карты из строки в класс case и карта из строки в функции, принимающая эти классы case в качестве входного параметра?

Сценарий, который я пытаюсь смоделировать, выглядит следующим образом. У меня есть пара классов, которые отличаются по своим параметрам, но все они расширяют эту черту Entity,

// case classes 
trait Entity
case class E1(..params..) extends Entity
case class E2(..params..) extends Entity
...
case class En(..params..) extends Entity

У меня есть набор функций, которые принимают один параметр, который является подтипом Entity, например, следующее (у нас больше функций, чем сущностей):

// functions using case classes as parameters
def f1(val p:E1) = ???
def f2(val p:E4) = ???
...
def fm(val p:E2) = ??? 

Теперь я получаю экземпляр Entity сериализован в String, и рядом с ним я получаю имя функции для вызова. Десериализация не проблема: допустим, у меня есть функция read[T](str) которые могут десериализовать str в объект типа T,

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

Я подумал, что мне понадобятся карты, подобные приведенным ниже, которые, учитывая имя функции, дадут мне саму функцию и тип ее параметра. Тогда я, в принципе, легко смогу позвонить, как показано ниже.

// the mappings from String to entity and corresponding function
val map1 = Map (
    "f1" -> f1
    "f2" -> f2
    ...
    "fn" -> fn
  ) 
}
val map2 = Map (
    "f1" -> E1
    "f2" -> E4
    ...
    "fn" -> E2
  ) 
}

def makeTheCall (fname: String, ent: String) = map1.get(fname)(read[map2.get(fname)](ent))
  1. Это не работает, потому что я не могу получить правильные типы (и определенно выведенные типы также не работают).

  2. Есть ли способ поставить map1 а также map2 вместе (чтобы было меньше шансов испортить отношения между функциями и типами параметров)?


РЕДАКТИРОВАТЬ: Ради простоты, мы можем здесь игнорировать параметры для сущностей и, следовательно, фактической сериализованной сущности. Это должно помочь написать скомпилированный код без особой работы.


РЕДАКТИРОВАТЬ: вариант использования: я пишу программу, которая получает сообщения от RabbitMQ. Тело сообщения содержит сущность, а ключ сообщения подразумевает, что с ней делать.

2 ответа

Решение

Редактировать 2: Используя функцию, аналогичную моей предыдущей редакции, вы можете создать карту с String => Unit функции, которые объединяют десериализацию и вашу функцию, поэтому вам не нужны две карты.

def deserializeAnd[E <: Entity](f: E => Unit): String => Unit = 
  (s: String) => f(read[E](s))

val behaviour = Map(
  "key1" -> deserializeAnd(println(_: Foo)),
  "key2" -> deserializeAnd(println(_: Bar)),
  "key3" -> deserializeAnd((foo: Foo) => println(foo.copy(a=0))
)

def processMessage(key: String, serialized: String): Option[Unit] = 
  behaviour.get(key).map(f => f(serialized))

// throws an exception if 'behaviour' doesn't contain the key
def processMessage2(key: String, serialized: String): Unit =
  behaviour(key)(serialized)

Редактировать: кажется, что у вас есть потенциально несколько функций с одним и тем же типом ввода, что делает его не лучшим вариантом использования для класса типа.

Вы можете использовать что-то вроде:

def makeTheCall[E <: Entity, Out](f: E => Out, s: String): Out = f(read[E](s))

Он десериализует вашу строку в соответствии с типом ввода переданной функции.
Который вы могли бы использовать как makeTheCall(f2, "serializedE4"),


Даже если бы вы могли найти правильные типы, чтобы получить makeTheCall метод работает, вы не должны использовать строки, чтобы различать несколько типов. Что делать, если вы сделаете опечатку в fname, map1 содержит fname а также map2 не...

Из вашего вопроса не совсем понятно, что именно вы хотите делать, но, похоже, класс типов подойдет для вашего случая. С помощью класса типов вы можете создать экземпляр с определенной функциональностью для типа, который может делать то, что вы хотите сделать с вашим f1, f2... функции.

Представь, что твой f1, f2... все функции возвращают Intмы могли бы создать класс типа, который содержит такую ​​функцию для Entity типы:

trait EntityOperation[E <: Entity] {
  def func(e: E): Int 
}

Давайте создадим некоторые классы дел, которые расширяют Entity:

trait Entity
case class Foo(a: Int, b: Int) extends Entity
case class Bar(c: String, d: String) extends Entity

Теперь мы можем создать экземпляр нашего класса типов для Foo а также Bar:

implicit val FooEntityOp = new EntityOperation[Foo] {
  def func(foo: Foo) : Int = foo.a + foo.b
}

implicit val BarEntityOp = new EntityOperation[Bar] {
  def func(bar: Bar) : Int = bar.c.length + bar.d.length
}

Мы могли бы использовать наш класс типов следующим образом:

def callF[E <: Entity](e: E)(implicit op: EntityOperation[E]) = op.func(e)

callF(Foo(1, 2))          // Int = 3
callF(Bar("xx", "yyyy"))  // Int = 6

В вашем случае это может выглядеть так:

def makeTheCall[E <: Entity](s: String)(implicit op: EntityOperation[E]) = 
  op.func(read[E](s))

// makeTheCall[Baz]("serializedBaz")

Для простоты я собираюсь предположить, что ни одна из ваших функций не возвращает значение, поэтому каждая функция имеет тип something => Unit, Кроме того, я не собираюсь рассматривать какую-либо обработку исключений, поэтому я ожидаю, что карты всегда будут возвращать совместимое значение.

Настройка (как описано в вопросе):

trait Entity

case class E1() extends Entity

case class E2() extends Entity

case class En() extends Entity

def f1(p: E1) = println(25)

def f2(p: En) = println("foo")

def fm(p: E2) = println(p.toString)

У нас также есть функция read: String => Entity который возвращает некоторый подтип Entityи хранилище (реализовано как Map) который хранит наши функции:

def read[T](s: String) = s match {
  case "E1" => E1()
  case "E2" => E2()
  case "En" => En()
}

val functionMap = Map[String, Nothing => Unit](
  "f1" -> f1 _,
  "f2" -> f2 _,
  "fm" -> fm _
)

Причина в том, что значение типа functionMap это функция от Nothing в Unit в том, что для вызова произвольной функции из карты аргумент должен быть подтипом всех типов аргументов. Тем не менее, единственный подтип E1 а также E2 является Nothing, Это также причина, почему вывод типов не помог вам: нет статически ни одного типа, который удовлетворял бы произвольной функции из карты.

Решение:

Если вы хотите придерживаться карты, нет решения, которое обеспечило бы вам статическую безопасность. Однако, если вы знаете, что делаете (или обрабатываете исключения во время выполнения), вы можете решить проблему, выполнив приведение во время выполнения. Во-первых, вы десериализуете Entity используя метод чтения. Затем вы решаете метод, который хотите вызвать, ища fName в карте функций. Если вы уверены, что функция и параметр совпадают, то вы можете привести эту функцию и порадовать компилятор:

def makeCall(fName: String, ent: String) = {
  val param = read(ent)
  functionMap.get(fName).get.asInstanceOf[param.type => Unit](param)
}
Другие вопросы по тегам