Reputation: 105
everyone!
I have a little problem with Scala pattern matching. I am using Korolev framework for my web application, but I bet problem is deeper in Scala as is.
I have a base trait for all states:
trait BaseState {
val notifications: List[Notification]
}
And two derived case classes:
case class GuestState(
notifications: List[Notification] = List(),
isRegistration: Boolean = false
) extends BaseState
and
case class AuthenticatedState(
notifications: List[Notification] = List(),
user: UserWithPermissions
) extends BaseState
In one of event handlers I need to get similar state without certain notification. For now it works like that:
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))
}
}
For both types I have to do absolutely same thing with duplicating of code because BaseState
does not have copy()
method and compiler fails with error.
How can I do it properly in scala-way? Thank you.
Upvotes: 2
Views: 334
Reputation: 4342
It is not solvable without changing base trait. Compiler cannot allow you to perform operation copy
on trait when it clearly does not have it.
One approach is to specify mapping/filtering function on BaseState as other answers suggest.
Another approach is to replace inheritance with composition:
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))
Upvotes: 2
Reputation: 499
I had similar scenarios in the past and never found a really elegant solution to this, as copy methods with identical or similar signatures cannot be easily expressed in a polymorphic way.
I usually added some plumbing to the trait and case classes so that the repetition is at least in one place:
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)
}
This is quite far from ideal though, and maybe there are better patterns.
But if you use logic like in your example a lot, it would at least reduce the boilerplate significantly. If the filter logic is also repeatedly used, you could add a more concrete filterNotififications
to the base trait.
The advantage of this solution compared to the other answer is that you do not lose type precision when filtering.
And remember to seal the trait wherever possible.
Upvotes: 2
Reputation: 27421
The two cases do not actually do the same thing, because the two copy
methods have different signatures. So while it may be textually the same, the code is actually doing something different in each case.
One option here is to add an abstract filterNotifications
method to the trait and call that:
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))
}
Upvotes: 2