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

问题描述

我想根据对象的类型对对象列表进行模式匹配。 但是将模式指定为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,并且大小写模式指定了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())) // All B's
func(List(C(),C(),C()))    // All C's
func(List(C(),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(),C()))    // Something else

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

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(),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(),C()))    // Something else

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

在无形状类型中定义类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())
val cs = List(C(),C())
val mixed = List(C(),C())

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

注意:您需要考虑将在类型类上公开的接口,以便您可以编写通用函数,但根据基础类型的不同,它们的行为也不同。


但是,请记住 typeclasses 是在编译时选择的,并且仅使用类型。因此,如果您具有List[A]类型的编译时间值,即使它充满了Bs,它也会选择"Something else"

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

您可以看到运行here的代码。