【问题标题】:Pattern matching for case classes derived from same trait从相同特征派生的案例类的模式匹配
【发布时间】:2018-09-07 19:50:48
【问题描述】:

大家!

我对 Scala 模式匹配有一点问题。我正在为我的 Web 应用程序使用 Korolev 框架,但我敢打赌,问题在 Scala 中更深层次。

我对所有州都有一个基本特征:

trait BaseState {
  val notifications: List[Notification]
}

还有两个派生的案例类:

case class GuestState(
                       notifications: List[Notification] = List(),
                       isRegistration: Boolean = false
                     ) extends BaseState

case class AuthenticatedState(
                          notifications: List[Notification] = List(),
                          user: UserWithPermissions
                        ) extends BaseState

在其中一个事件处理程序中,我需要在没有特定通知的情况下获得类似的状态。现在它是这样工作的:

event('click) { access =>
              access.transition {
                case s: GuestState => s.copy(notifications = notifications.filter(x => x != n))
                case s: AuthenticatedState => s.copy(notifications = notifications.filter(x => x != n))
              }
          }

对于这两种类型,我必须对复制代码做完全相同的事情,因为BaseState 没有copy() 方法并且编译器失败并出现错误。

我怎样才能以scala方式正确地做到这一点?谢谢你。

【问题讨论】:

    标签: scala pattern-matching


    【解决方案1】:

    这两种情况实际上并没有做同样的事情,因为这两个copy 方法有不同的签名。因此,虽然它可能在文本上相同,但代码实际上在每种情况下都在做不同的事情。

    这里的一个选择是向特征添加一个抽象的filterNotifications 方法并调用它:

    trait BaseState {
      val notifications: List[Notification]
      def filterNotifications(f: Notification => Boolean): BaseState
    }
    
    case class GuestState(
      notifications: List[Notification] = List(),
      isRegistration: Boolean = false
    ) extends BaseState {
      def filterNotifications(f: Notification => Boolean): BaseState =
        this.copy(notifications=notifications.filter(f))
    }
    
    case class AuthenticatedState(
      notifications: List[Notification] = List(),
      user: UserWithPermissions
    ) extends BaseState {
      def filterNotifications(f: Notification => Boolean): BaseState =
        this.copy(notifications=notifications.filter(f))
    }
    

    【讨论】:

      【解决方案2】:

      我过去也遇到过类似的情况,但从未找到真正优雅的解决方案,因为具有相同或相似签名的复制方法无法以多态方式轻松表达。

      我通常会在 trait 和 case 类中添加一些管道,以便至少在一个地方重复:

      trait BaseState {
        type ThisType <: BaseState
        val notifications: List[Notification]
        def withNotifications(notifications: List[Notification]): ThisType
      }
      
      case class GuestState(notifications: List[Notification] = List(),
                          isRegistration: Boolean = false) extends BaseState {
        type ThisType = GuestState
        def withNotifications(notifications: List[Notification]): ThisType = 
          copy(notifications = notifications)
      }
      
      case class AuthenticatedState(notifications: List[Notification] = List(),
                                  user: UserWithPermissions) extends BaseState {
        type ThisType = AuthenticatedState
        def withNotifications(notifications: List[Notification]): ThisType = 
          copy(notifications = notifications)
      }
      

      但这远非理想,也许还有更好的模式。 但是,如果您在示例中大量使用逻辑,则至少会显着减少样板。如果过滤器逻辑也被重复使用,您可以在基本特征中添加更具体的filterNotififications

      与其他答案相比,此解决方案的优势在于过滤时不会丢失类型精度。

      并记住尽可能密封特征。

      【讨论】:

        【解决方案3】:

        不改变基本特征是无法解决的。编译器不能让你对 trait 执行操作 copy,因为它显然没有它。

        一种方法是按照其他答案的建议在 BaseState 上指定映射/过滤功能。

        另一种方法是用组合代替继承:

        case class Notified(state: BaseState, notifications: List[Notification] = List())
        
        sealed trait BaseState
        case class GuestState(isRegistration: Boolean = false) extends BaseState 
        case class AuthenticatedState(user: UserWithPermissions) extends BaseState
        
        val notified = Notified(GuestState())
        val result = notified.copy(notifications = notifications.filter(_ => true))
        

        【讨论】:

          猜你喜欢
          • 2020-11-19
          • 2011-11-27
          • 2020-11-28
          • 2019-09-24
          • 2014-09-23
          • 2013-11-28
          • 2012-01-02
          • 1970-01-01
          • 2017-02-25
          相关资源
          最近更新 更多