如何对列表元素的类型进行模式匹配

How to pattern match on the types of list elements

我想根据对象的类型对对象列表进行模式匹配。 但是将模式指定为 case x: List[ObjectType] 似乎不起作用。

以这个程序为例

sealed trait A
case class B() extends A
case class C() extends A

def func(theList: List[A]) = theList match
{
    case listOfB: List[B] => println("All B's")
    case listOfC: List[C] => println("All C's")
    case _ => println("Somthing else")
}

func(List(C(), C(), C())) // prints: "All B's"

虽然列表只包含C,case模式指定了B的列表,但是match语句将其识别为B的列表?

我知道我可以像这样检查列表的每个元素:

case listOfA: List[A] if listOfA.forall{case B() => true case _ => false} => println("All B's")

但是比较麻烦,我在尝试使用的时候必须说明它确实是一个B的列表(listOfA.asInstanceOf[List[B]])

我怎样才能以更聪明/更好的方式做到这一点?

尝试自定义提取器以减少模式匹配的麻烦

object AllB {
  def unapply(listOfA: List[A]): Boolean = 
    listOfA.forall { case B() => true; case _ => false }
}
object AllC {
  def unapply(listOfA: List[A]): Boolean = 
    listOfA.forall { case C() => true; case _ => false }
}

def func(theList: List[A]) = theList match {
  case AllB() => println("All B's")
  case AllC() => println("All C's")
  case _      => println("Something else")
}

func(List(B(), B(), B()))    // All B's
func(List[A](B(), B(), B())) // All B's
func(List(C(), C(), C()))    // All C's
func(List(C(), B(), C()))    // Something else

import cats.implicits._

object AllB {
  def unapply(listOfA: List[A]): Option[List[B]] = 
    listOfA.traverse { case b@B() => Some(b); case _ => None }
}
object AllC {
  def unapply(listOfA: List[A]): Option[List[C]] = 
    listOfA.traverse { case c@C() => Some(c); case _ => None }
}

def func(theList: List[A]) = theList match {
  case AllB(listOfB) => println("All B's")
  case AllC(listOfC) => println("All C's")
  case _             => println("Something else")
}

func(List(B(), B(), B()))    // All B's
func(List[A](B(), B(), B())) // All B's
func(List(C(), C(), C()))    // All C's
func(List(C(), B(), C()))    // Something else

或者您可以定义一个 class 来创建所有必要的提取器并删除代码重复

class All[SubT: ClassTag] {
  def unapply[T >: SubT](listOfA: List[T]): Option[List[SubT]] = 
    listOfA.traverse { case x: SubT => Some(x); case _ => None }
}

object AllB extends All[B]
object AllC extends All[C]
// val AllB = new All[B]
// val AllC = new All[C]

def func(theList: List[A]) = theList match {
  case AllB(listOfB) => println("All B's")
  case AllC(listOfC) => println("All C's")
  case _             => println("Something else")
}

func(List(B(), B(), B()))    // All B's
func(List[A](B(), B(), B())) // All B's
func(List(C(), C(), C()))    // All C's
func(List(C(), B(), C()))    // Something else

我想,最简单的就是使用 Shapeless

import shapeless.TypeCase

val AllB = TypeCase[List[B]]
val AllC = TypeCase[List[C]]

def func(theList: List[A]) = theList match {
  case AllB(listOfB) => println("All B's")
  case AllC(listOfC) => println("All C's")
  case _             => println("Something else")
}

func(List(B(), B(), B()))    // All B's
func(List[A](B(), B(), B())) // All B's
func(List(C(), C(), C()))    // All C's
func(List(C(), B(), C()))    // Something else

https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#type-safe-cast

在 Shapeless 类型中定义了 class Typeable。只是它的列表实例定义比 @LuisMiguelMejíaSuárez 的答案(即使用运行时反射)

/** Typeable instance for `Traversable`.    
 *  Note that the contents be will tested for conformance to the element type. */  
implicit def genTraversableTypeable[CC[X] <: Iterable[X], T]
  (implicit mCC: ClassTag[CC[_]], castT: Typeable[T]): Typeable[CC[T] with Iterable[T]] =
  // Nb. the apparently redundant `with Iterable[T]` is a workaround for a
  // Scala 2.10.x bug which causes conflicts between this instance and `anyTypeable`.
  new Typeable[CC[T]] {
    def cast(t: Any): Option[CC[T]] =
      if(t == null) None
      else if(mCC.runtimeClass isInstance t) {
        val cc = t.asInstanceOf[CC[Any]]
        if(cc.forall(_.cast[T].isDefined)) Some(t.asInstanceOf[CC[T]])
        else None
      } else None
    def describe = s"${safeSimpleName(mCC)}[${castT.describe}]"
  }

https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala#L235-L250

另请参阅在 Scala 中模式匹配泛型类型的方法 https://gist.github.com/jkpl/5279ee05cca8cc1ec452fc26ace5b68b

假设您在编译时有一个 List[B] 或一个 List[C] 并且您想以不同的方式操作它们,您可以使用 typeclass.

像这样:

trait MyTypeClass[T] {
  def process(data: List[T]): String
}

sealed trait A extends Product with Serializable
final case class B() extends A
final case class C() extends A
object A extends ALowerPriority {
  implicit final val AllOfB: MyTypeClass[B] =
    (_: List[B]) => "All B's"
  
  implicit final val AllOfC: MyTypeClass[C] =
    (_: List[C]) => "All C's"
}

trait ALowerPriority {
  implicit final val Mixed: MyTypeClass[A] =
    (_: List[A]) => "Somenthing else"
}

def func[T](theList: List[T])
          (implicit ev: MyTypeClass[T]): Unit =
  println(ev.process(data = theList))

它是这样工作的:

val bs = List(B(), B(), B())
val cs = List(C(), C(), C())
val mixed = List(C(), B(), C())

func(bs) // All B's
func(cs) // All C's
func(mixed) // Something else

注意:您需要考虑在您的类型类上公开的接口是什么,这样您就可以编写通用函数,但它们的行为会根据底层类型而有所不同。


但是,请记住 类型类 是在编译时选择的并且只使用类型。所以,如果你有一个 List[A] 类型的编译时值,即使它充满了 Bs 它也会选择 "Something else":

val as: List[A] = List(B(), B(), B())
func(as) // Something else

可以看到代码运行 here.