优化 case class 用作符号

Optimizing case class use as symbols

我正在使用 Java API 将标识符作为字符串传递。对我来说使用类型符号似乎更好一些,所以我写了这个:

  object Helpers {
    implicit def actionToString(action: Action): String =
      action.getClass.getName.stripSuffix("$").replaceAll(".*\$", "")

    object Action{
      def apply(name: String): Action = {
        Class.forName("tutorial.HelloInput$Helpers$" + name).newInstance()
          .asInstanceOf[Action]
      }
    }
    sealed abstract class Action {
      override def toString: String = actionToString(this)
    }
    final case class Rotate() extends Action
    final case class Right() extends Action
    final case class Left() extends Action
    final case class Pause() extends Action
  }

这允许 "serialization" 和 "deserialization" 以自然的方式进行字符串和操作,例如,我可以在 Action(Pause) 上进行模式匹配,但我也可以传递 Pause()由于隐式转换,需要一个字符串的 Java 库。

有没有更好的方法来做到这一点,尤其是在性能方面?这个方法有什么问题以后可能会回来咬我吗?

我阅读了一些关于 Dotty 中的 Phantom 类型的内容,想知道它们是否可以用来提高处理符号的性能(或者新的枚举可能是更好的选择)。

隐式转换有反噬你的习惯。在这种情况下,这意味着您可以在需要 String 的任何方法中使用您的一种操作类型,而不仅仅是您想要的地方。它也不会阻止您将一些其他字符串传递到库中。

我不会使用转换直接与 Java 库交互,而是围绕它创建一个包装器来接受和 returns 您的操作类型。这样你就得到了一个漂亮的类型 api 并且不需要任何转换。

由于您的 classes 没有参数,因此使用案例对象是有意义的,因此您只需要为每种操作类型创建一个实例,而不是每次都创建一个新实例。

我也关心反射的使用。在从字符串创建操作时,可以使用模式匹配来实现,并且可以通过让每种类型定义它的字符串值来完成到字符串的转换,或者如果 class 的名称始终与您需要的字符串(尽管我更愿意明确定义它)。我怀疑这种方法也会更快,但您需要对其进行基准测试才能确定。

与以下方法相比,您的方法有哪些优势:?

object Helpers {
  type Action = String
  val Rotate: Action = "Rotate"
  val Right:  Action = "Right"
  val Left:   Action = "Left"
  val Pause:  Action = "Pause"
}

您可以使用值 class:

将样板换成类型安全(不会损失任何性能)
object Helpers {
  // This won't allocate anything more than the previous solution!
  final case class Action(name: String) extends AnyVal
  val Rotate: Action = Action("Rotate")
  val Right:  Action = Action("Right")
  val Left:   Action = Action("Left")
  val Pause:  Action = Action("Pause")
}

我认为以上两种方式都比使用反射+隐式转换更健壮。例如,当移动代码或重命名 tutorial 包时,您的解决方案将无声地中断。

Are there any problems with this method that may come back to bite me later?

隐式转换是魔鬼。我强烈建议你永远不要在你的设计中考虑它们,很容易 "get something that works" 并在一两周后后悔......事实上,使用你的解决方案,以下编译将是 IMO 不可接受的:

val myAction: Action = ...
myAction.foreach(...)
myAction.map(...)

I was reading a little about Phantom types in Dotty, and wonder if they might be used to improve performance for dealing with symbols (or maybe the new Enums would be the better alternative).

我认为联合类型+文字单例类型在这种情况下可能很有用,您可以定义以下类型别名:

type Action = "Rotate" | "Right" | "Left" | "Pause"

那么如果一个方法只应该使用这些类型的一个子集来调用,那么您可以将其非常精确地放在它的 API 中! (除了我们不能在 or 类型中使用文字单例类型,但我确信它们在某些时候会受到支持,请参阅 this issue)。枚举只是你已经可以用 sealed trait + case classes 做的语法,除了 "save a few keystrokes".

之外,它们应该没有任何帮助