Reputation: 4884
I have the following code (simplified):
sealed trait Funding {
id: Int
}
final case class FlatRate(id: Int, days: Int, amount: BigDecimal) extends Funding
final case class PerItem(id: Int, amount: BigDecimal, discount: BigDecimal) extends Funding
final case class AppliedFunding[+A <: Funding](
person: String
)
I have some code that returns a list of "generic" AppliedFunding
and I would like to get a specific AppliedFunding
with the correct type. Pseudo-code:
val allFunding: List[AppliedFunding[Funding]] = <...>
val flatrateFunding: List[AppliedFunding[FlatRate]] = allFunding.someMagicFunction
In place of someMagicFunction
I am looking for a way to filter the correct AppliedFunding
instances out and return them with the specific type. I can do allFunding.collect { ... }
, but no matter what I tried it would still return List[AppliedFunding[Funding]]
.
Any help is much appreciated.
Upvotes: 3
Views: 300
Reputation: 3435
Below is a solution on how this can be done with help of isInstanceOf
and asInstanceOf
. Another working solution is provided by Dominic in other answer, but for filtering use case I am not sure that creating a new case class instance for each item which passes the filter is a proper way to go. Moreover, desugaring with scalac -print
shows that isInstanceOf
invocation is not going anywhere in that case, it will be embedded in generated isDefinedAt
and applyOrElse
methods for the partial function used in collect
.
Some people say that using isInstanceOf
and asInstanceOf
is a code smell but for me it looks that in this particular case usage is safe and correct and brings no single benefit over the solution with collect
: invocation of collect
just hides isInstanceOf
in the generated source code and instantiating new case classes instances is a trade off for a safe call of asInstanceOf
on a filtered collection.
Also please pay attention that I've added a funding
field to the AppliedFunding
case class. I think that having AppliedFunding
instances without embedded info about funding itself is not very useful anyway.
object Test {
sealed trait Funding {
val id: Int
}
final case class FlatRate(id: Int, days: Int, amount: BigDecimal) extends Funding
final case class PerItem(id: Int, amount: BigDecimal, discount: BigDecimal) extends Funding
final case class AppliedFunding[+A <: Funding](
person: String, funding: A)
val allFundings: List[AppliedFunding[Funding]] = List(AppliedFunding[FlatRate]("alex", FlatRate(1, 10, 100)), AppliedFunding[PerItem]("christian", PerItem(2, 100, 10)))
def flatRatePredicate[A <: Funding](appliedFunding: AppliedFunding[A]): Boolean = appliedFunding.funding.isInstanceOf[FlatRate]
val flatRateFundings: List[AppliedFunding[FlatRate]] = allFundings.filter(flatRatePredicate).asInstanceOf[List[AppliedFunding[FlatRate]]]
def main(args: Array[String]): Unit = {
println(flatRateFundings)
}
}
Upvotes: 0
Reputation: 1016
below are 2 ways how to do it. Just a sideremark: collect
is one of the very few situations where PartialFunction
is a good thing
sealed trait Foo
final case class Bar(i:Int) extends Foo
final case class Baz(d:Double) extends Foo
final case class Container[F <: Foo](s:String, f:F)
val all:List[Container[Foo]] = List(
Container("bar", Bar(3)),
Container("bar", Bar(4)),
Container("bar", Bar(5)),
Container("bar", Bar(6)),
Container("bar", Bar(7)),
Container("baz", Baz(1.5)),
Container("baz", Baz(2.5)),
Container("baz", Baz(3.5)),
Container("baz", Baz(4.5)),
Container("baz", Baz(5.5))
)
val bars:List[Container[Bar]] = all.collect { case Container(s, Bar(i)) => Container(s, Bar(i))}
val baz:List[Container[Baz]] = all.foldRight(List.empty[Container[Baz]]) (
(elem, lst) => elem match {
case Container(s, Baz(d)) => Container(s, Baz(d)) :: lst
case _ => lst
}
)
println(bars)
println(baz)
Upvotes: 3