在 Scala 中,从字符串到 case class 的映射类型以及从字符串到以这些 case classes 作为输入参数的函数的映射应该是什么类型?
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?
我尝试建模的场景如下。我有几个 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
序列化为字符串的实例,在它旁边我得到调用它的函数的名称。反序列化不是问题:假设我有一个函数 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))
这是行不通的,因为我无法获得正确的类型(当然推断类型也行不通)。
有没有办法把map1
和map2
放在一起(这样就不会弄乱函数和参数类型之间的关系)?
编辑:为了简单起见,我们可以在这里忽略实体的参数,因此忽略实际的序列化实体。这应该有助于编写可编译的代码而无需太多工作。
编辑:用例:我正在编写一个从 RabbitMQ 接收消息的程序。消息正文包含实体,消息密钥暗示如何处理它。
为简单起见,我假设 none 的函数 return 是一个值,因此每个函数的类型都是 something => Unit
。此外,我不打算考虑任何异常处理,所以我希望映射始终 return 兼容值。
设置(如问题中所述):
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
return 是 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
。这也是类型推断对您没有帮助的原因:静态地没有满足映射中任意函数的类型。
一个解决方案:
如果您想坚持地图,没有任何解决方案可以为您提供静态安全。但是,如果您知道自己在做什么(或在运行时处理异常),则可以通过执行运行时转换来解决问题。首先,使用 read 方法反序列化 Entity
。然后,通过在函数映射中查找 fName
来解析要调用的方法。如果您确定函数和参数匹配,那么您可以强制转换此函数并让编译器满意:
def makeCall(fName: String, ent: String) = {
val param = read(ent)
functionMap.get(fName).get.asInstanceOf[param.type => Unit](param)
}
编辑 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)
Edit:看起来您可能有多个具有相同输入类型的函数,这使得它不是类型 class.[=33 的好用例=]
你可以使用类似的东西:
def makeTheCall[E <: Entity, Out](f: E => Out, s: String): Out = f(read[E](s))
它将您的字符串反序列化为传递函数的输入类型。
您可以将其用作 makeTheCall(f2, "serializedE4")
.
即使您可以找到使 makeTheCall
方法正常工作的正确类型,您也不应使用字符串来区分多种类型。如果您在 fname
中输入错误,map1
包含 fname
而 map2
不包含怎么办,...?
从你的问题中并不清楚你到底想做什么,但 class 类型似乎很适合你的情况。使用类型 class,您可以为类型创建一个具有特定功能的实例,它可以执行您想要对 f1
、f2
、... 函数执行的操作。
假设您的 f1
、f2
、...函数全部 return 和 Int
,我们可以创建一个类型 class,其中包含这样的Entity
类型的函数:
trait EntityOperation[E <: Entity] {
def func(e: E): Int
}
让我们创建一些 classes 扩展 Entity
:
trait Entity
case class Foo(a: Int, b: Int) extends Entity
case class Bar(c: String, d: String) extends Entity
现在我们可以为 Foo
和 Bar
创建类型 class 的实例:
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
}
我们可以使用我们的类型 class 如下:
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")
我尝试建模的场景如下。我有几个 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
序列化为字符串的实例,在它旁边我得到调用它的函数的名称。反序列化不是问题:假设我有一个函数 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))
这是行不通的,因为我无法获得正确的类型(当然推断类型也行不通)。
有没有办法把
map1
和map2
放在一起(这样就不会弄乱函数和参数类型之间的关系)?
编辑:为了简单起见,我们可以在这里忽略实体的参数,因此忽略实际的序列化实体。这应该有助于编写可编译的代码而无需太多工作。
编辑:用例:我正在编写一个从 RabbitMQ 接收消息的程序。消息正文包含实体,消息密钥暗示如何处理它。
为简单起见,我假设 none 的函数 return 是一个值,因此每个函数的类型都是 something => Unit
。此外,我不打算考虑任何异常处理,所以我希望映射始终 return 兼容值。
设置(如问题中所述):
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
return 是 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
。这也是类型推断对您没有帮助的原因:静态地没有满足映射中任意函数的类型。
一个解决方案:
如果您想坚持地图,没有任何解决方案可以为您提供静态安全。但是,如果您知道自己在做什么(或在运行时处理异常),则可以通过执行运行时转换来解决问题。首先,使用 read 方法反序列化 Entity
。然后,通过在函数映射中查找 fName
来解析要调用的方法。如果您确定函数和参数匹配,那么您可以强制转换此函数并让编译器满意:
def makeCall(fName: String, ent: String) = {
val param = read(ent)
functionMap.get(fName).get.asInstanceOf[param.type => Unit](param)
}
编辑 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)
Edit:看起来您可能有多个具有相同输入类型的函数,这使得它不是类型 class.[=33 的好用例=]
你可以使用类似的东西:
def makeTheCall[E <: Entity, Out](f: E => Out, s: String): Out = f(read[E](s))
它将您的字符串反序列化为传递函数的输入类型。
您可以将其用作 makeTheCall(f2, "serializedE4")
.
即使您可以找到使 makeTheCall
方法正常工作的正确类型,您也不应使用字符串来区分多种类型。如果您在 fname
中输入错误,map1
包含 fname
而 map2
不包含怎么办,...?
从你的问题中并不清楚你到底想做什么,但 class 类型似乎很适合你的情况。使用类型 class,您可以为类型创建一个具有特定功能的实例,它可以执行您想要对 f1
、f2
、... 函数执行的操作。
假设您的 f1
、f2
、...函数全部 return 和 Int
,我们可以创建一个类型 class,其中包含这样的Entity
类型的函数:
trait EntityOperation[E <: Entity] {
def func(e: E): Int
}
让我们创建一些 classes 扩展 Entity
:
trait Entity
case class Foo(a: Int, b: Int) extends Entity
case class Bar(c: String, d: String) extends Entity
现在我们可以为 Foo
和 Bar
创建类型 class 的实例:
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
}
我们可以使用我们的类型 class 如下:
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")