簡體   English   中英

在Scala中,以字符串到案例類的映射以及以這些案例類作為輸入參數的從字符串到函數的映射的類型應該是什么?

[英]In Scala, what should be the type of a map from string to case class, and a map from string to functions taking those case classes as input parameter?

我嘗試建模的場景如下。 我有幾個案例類,它們的參數不同,但是它們都擴展了特征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) = ??? 

現在,我獲得了一個序列化為String的Entity實例,並在其旁邊獲得了要調用的函數的名稱。 反序列化不是問題:假設我有一個函數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. 有沒有辦法將map1map2放在一起(這樣就不太可能弄亂函數和參數類型之間的關系)?


編輯:為了簡單起見,我們在這里可以忽略實體的參數,因此也可以忽略實際的序列化實體。 這應該有助於編寫無需太多工作的可編譯代碼。


編輯:用例:我正在編寫一個從RabbitMQ接收消息的程序。 消息的主體包含該實體,並且消息密鑰暗示了如何處理它。

編輯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包含fnamemap2沒有,...?

從您的問題中並不清楚您要確切執行什么操作,但似乎類型類將非常適合您的情況。 使用類型類,您可以為類型創建具有特定功能的實例,這可以像使用f1f2 ,...函數一樣進行操作。

假設您的f1f2 ,...函數都返回一個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

現在,我們可以為FooBar創建類型類的實例:

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的值類型是從NothingUnitfunctionMap的原因是,為了從映射中調用任意函數,自變量必須是所有自變量類型的子類型。 但是, E1E2的唯一子類型是Nothing 這也是為什么類型推斷不能幫助您的原因:靜態地沒有類型可以滿足映射中的任意函數。

一個解法:

如果要堅持使用地圖,沒有解決方案可以為您提供靜態安全性。 但是,如果您知道自己在做什么(或正在運行時處理異常),則可以解決在進行運行時強制轉換的問題。 首先,您使用read方法反序列化Entity 然后,通過在函數映射中查找fName來解析要調用的方法。 如果確定函數和參數匹配,則可以強制轉換該函數並使編譯器滿意:

def makeCall(fName: String, ent: String) = {
  val param = read(ent)
  functionMap.get(fName).get.asInstanceOf[param.type => Unit](param)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM