【发布时间】:2017-09-24 04:32:26
【问题描述】:
我目前正在尝试在运行时动态创建 Akka 流图定义。这个想法是用户将能够以交互方式定义流并将它们附加到现有/正在运行的BroadcastHubs。这意味着我不知道在编译时将使用哪些流甚至多少流。
不幸的是,我正在为泛型/类型擦除而苦苦挣扎。坦率地说,我什至不确定我试图在 JVM 上做些什么。
我有一个函数将返回一个 Akka Streams Flow 代表两个连接的 Flows。它使用 Scala 的TypeTags 来绕过类型擦除。如果第一个流的输出类型与第二个流的输入类型相同,则可以连接成功。这工作得很好。
import akka.NotUsed
import akka.stream.FlowShape
import akka.stream.scaladsl.GraphDSL.Implicits._
import akka.stream.scaladsl.{Flow, GraphDSL}
import scala.reflect.runtime.universe._
import scala.util.{Failure, Success, Try}
def connect[A: TypeTag, B: TypeTag, C: TypeTag, D: TypeTag](a: Flow[A, B, NotUsed],
b: Flow[C, D, NotUsed]): Try[Flow[A, D, NotUsed]] = {
Try {
if (typeOf[B] =:= typeOf[C]) {
val c = b.asInstanceOf[Flow[B, D, NotUsed]]
Flow.fromGraph {
GraphDSL.create(a, c)((m1, m2) => NotUsed.getInstance()) { implicit b =>
(s1, s2) =>
s1 ~> s2
FlowShape(s1.in, s2.out)
}
}
}
else
throw new RuntimeException(s"Connection failed. Incompatible types: ${typeOf[B]} and ${typeOf[C]}")
}
}
所以如果我有Flow[A,B] 和Flow[C,D],则结果将是Flow[A,D],假设B 和C 是同一类型。
我还具有尝试将List 的Flows 合并/减少为单个Flow 的功能。让我们假设此列表源自文件或 Web 请求的流定义列表。
def merge(fcs: List[Flow[_, _, NotUsed]]): Try[Option[Flow[_, _, NotUsed]]] = {
fcs match {
case Nil => Success(None)
case h :: Nil => Success(Some(h))
case h :: t =>
val n = t.head
connect(h, n) match {
case Success(fc) => merge(fc :: t)
case Failure(e) => Failure(e)
}
}
}
不幸的是,由于Flows 存储在List 中,由于标准Lists 上的类型擦除,我丢失了所有类型信息,因此无法在运行时连接Flows。这是一个例子:
def flowIdentity[A]() = Flow.fromFunction[A, A](x => x)
def flowI2S() = Flow.fromFunction[Int, String](_.toString)
val a = flowIdentity[Int]()
val b = flowIdentity[Int]()
val c = flowI2S()
val d = flowIdentity[String]()
val fcs: List[Flow[_, _, NotUsed]] = List(a, b, c, d)
val y = merge(fcs)
这会导致异常:
Failure(java.lang.RuntimeException: Connection failed. Incompatible types _$4 and _$3)
我一直在研究 Miles Sabin 的Shapeless,并认为我可以使用HLists 来保留类型信息。不幸的是,这似乎只有在我在编译时知道列表的各个类型和长度时才有效。如果我将特定的 HList 向上转换为 HList,看起来我又丢失了类型信息。
val fcs: HList = a :: b :: c :: d :: HNil
所以我的问题是……这可能吗?有没有办法用无形泛型魔法来做到这一点(最好不需要使用特定的非存在类型提取器)?我希望找到尽可能通用的解决方案,我们将不胜感激。
谢谢!
【问题讨论】:
-
首先,在第一个代码示例中,所有类型在编译时都是已知的,因此可以将其简化为 3 种类型而不是 4 种:
Flow[A, B]和Flow[B, C] -
只是好奇:您是否考虑过编译用户创建的图表?即从一些 DSL 生成 scala 代码,然后编译,然后加载到运行时?
-
这实际上是一个好主意...我没想到。这将在有人创建图表时提供所需的类型安全性。现在我只需要处理类加载器魔法......谢谢!
标签: scala akka shapeless akka-stream type-erasure