简体   繁体   中英

Scala - Idiomatic way of creating a collection of types

Problem

Let's says that I have an ADT that look like that

sealed trait TT
case class A(...) extends TT
case class B(...) extends TT
case class C(...) extends TT
// ... lot of others

And I have this function that return true for of subset of TT . Let's say A and C .

def shouldIPublish(tt: TT): Boolean = ???

Constraints

  • the value tt we receive comes from a SortedSet that I can't really change. Here is how the function shouldIPublish is called : tts.exists(shouldIPublish) ( tts is a SortedSet[TT] ). It is erased.
  • I would like that my code to be testable. Which is not possible with the solution I have for the moment. (see below)
  • The code should compile for every TT type. If the actual type of tt is in a subset (ie A or C ) return true otherwise false .

My imperfect solution

val shouldIPublishPF: PartialFunction[TT, Unit] = {
    case _: A =>
    case _: C =>
} 
def shouldIPublish(tt: TT): Boolean = shouldIPublishPF.isDefinedAt(tt)

I use partial functions here because the actual problem is a bit more complex. I use orElse to combine several partial functions together.

This solution is easy to reason about and simple enough. But,

  • it does not seem very idiomatic. I means this tool (Partial Functions) does not seem to be the right one for the job (Testing if type is contained in a group of type).
  • I can't get every value defined for the partial function . In that case it would be A and C . My unit test should fail when a new type is added to the partial function.

Alternatives (I can think of)

Set of classes

Maybe the simplest solution. I could have a Set and get class with .getClass . It seems ugly though.

Set(A.getClass, C.getClass)

Shapeless Coproduct

Looked quite promising at first. Very idiomatic.

type ShoudIPublish = A :+: C :+: CNil

Unfortunately, I don't see how to test that a type is "contained" in ShoudIPublish . And I don't know if we can collect the list of all types (here A and C ).


Do you have any suggestion?

Type class looks like a solution (if you know the type of tt ie specific subtype of TT at compile time)

trait ShouldIPublish[T <: TT]
object ShouldIPublish {
  implicit val a: ShouldIPublish[A] = null
  implicit val c: ShouldIPublish[C] = null
}

def shouldIPublish[T <: TT : ShouldIPublish](tt: T) = ???

shouldIPublish(A())
//  shouldIPublish(B()) // doesn't compile
shouldIPublish(C())

Standard type class is shapeless.ops.coproduct.Inject

type ShoudIPublish = A :+: C :+: CNil

def shouldIPublish[T <: TT : Inject[ShoudIPublish, *]](tt: T) = ???

shouldIPublish(A())
//  shouldIPublish(B()) // doesn't compile
shouldIPublish(C())

OOP-style solution would be to use a trait (if you can modify the hierarchy)

trait ShouldIPublish

sealed trait TT
case class A() extends TT with ShouldIPublish
case class B() extends TT
case class C() extends TT with ShouldIPublish 

def shouldIPublish[T <: TT with ShouldIPublish](tt: T) = ???

shouldIPublish(A())
//  shouldIPublish(B()) // doesn't compile
shouldIPublish(C())

Pattern-matching solution can work at runtime when you don't know specific subtype of TT at compile time

def shouldIPublish(tt: TT) = tt match {
  case _: A => println("A")
  case _: C => println("C")
}

shouldIPublish(A(): TT)// A
shouldIPublish(B(): TT)// MatchError
shouldIPublish(C(): TT)// C

or

trait ShouldIPublish

sealed trait TT
case class A() extends TT with ShouldIPublish
case class B() extends TT
case class C() extends TT with ShouldIPublish 

def shouldIPublish(tt: TT) = tt match {
  case _: ShouldIPublish => println("A or C")
}

shouldIPublish(A(): TT)// A or C
shouldIPublish(B(): TT)// MatchError
shouldIPublish(C(): TT)// A or C

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM