scala - 如何缩小状态机编码中上限类型参数的类型?

标签 scala functional-programming fsm algebraic-data-types

假设我有一个Cake可以在多种状态之间循环:

sealed trait State extends Product with Serializable
object State {
    final case object Raw extends State
    final case class JustRight(temperature: Int) extends State
    final case class Burnt(charCoalContent: Double) extends State
}
final case class Cake[S <: State](name: String, state: S)

这很好,因为现在我可以确保我只尝试输入 Raw将蛋糕放入 toastr ,而不是立即食用。

但有时我只有 Cake[State]躺在周围并想尝试吃它,但前提是它处于可食用状态。我当然可以始终在 cake.state 上进行模式匹配,但我认为应该可以通过添加方法 def narrow[S <: State]: Cake[State] => Option[Cake[S]] 来节省自己的几次击键次数.

但是,现在我正在努力实际实现该功能。编译器接受 Try(cake.asInstanceOf[Cake[S]]).toOption ,但似乎总是会成功(我猜是因为类型参数被删除,实际上任何类型 A 都会在这里被接受,而不仅仅是 S )。似乎有效的是 Try(cake.copy(state = cake.state.asInstanceOf[S])).toOption ,但现在我已经制作了多余的数据副本。还有其他更好的方法吗?或者整个编码从一开始就有缺陷?

最佳答案

您可以使用类型类来解决此问题,该类型类(以类型安全的方式)检查并强制转换状态的类型。

sealed trait State extends Product with Serializable
object State {
    final case object Raw extends State
    type Raw = Raw.type
    final case class JustRight(temperature: Int) extends State
    final case class Burnt(charCoalContent: Double) extends State
  
    sealed trait Checker[S <: State] {
      def check(state: State): Option[S]
    }
    object Checker {
      private def instance[S <: State](pf: PartialFunction[State, S]): Checker[S] =
        new Checker[S] {
          val f = pf.lift
          override def check(state: State): Option[S] = f(state)
        }
      
      implicit final val RawChecker: Checker[Raw] = instance {
        case Raw => Raw
      }
      
      implicit final val JustRightChecker: Checker[JustRight] = instance {
        case s @ JustRight(_) => s
      }
      
      implicit final val BurntChecker: Checker[Burnt] = instance {
        case s @ Burnt(_) => s
      }
    }
}

final case class Cake[S <: State](name: String, state: S)

def narrow[S <: State](cake: Cake[State])(implicit checker: State.Checker[S]): Option[Cake[S]] =
  checker.check(cake.state).map(s => cake.copy(state = s))

你可以这样使用:

val rawCake: Cake[State] = Cake(name = "Foo", state = State.Raw)

narrow[State.Raw](rawCake)
// res: Option[Cake[State.Raw]] = Some(Cake(Foo,Raw))
narrow[State.JustRight](rawCake)
// res: Option[Cake[State.JustRight] = None

顺便说一句,如果您想避免复制,您可以将check更改为仅返回Boolean并使用脏asInstanceOf.

// Technically speaking it is unsafe, but it seems to work just right.
def narrowUnsafe[S <: State](cake: Cake[State])(implicit checker: State.Checker[S]): Option[Cake[S]] =
  if (checker.check(cake.state)) Some(cake.asInstanceOf[Cake[S]])
  else None

(您可以看到正在运行的代码 here )

关于scala - 如何缩小状态机编码中上限类型参数的类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64715971/

相关文章:

mysql - 如何生成行号作为现有表的列?

vhdl - VHDL FSM 中的状态管理

uml - UML 状态图中具有相同开始/结束的转换

swift - 通用数组上的函数

VHDL:Mealy FSM 不在时钟边沿产生状态变化?

scala - 如何将 List[Double] 转换为 Columns?

scala - Scala 的 Netbeans 类型信息

scala - 将 Akka 库添加到 Intellij 项目

javascript - 函数式编程中连接等线程资源的处理方法

scala - 在 Scala 中正确实现 2 类型参数 Functor