Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

scala - Pattern matching with shapeless coproduct

Can I use pattern matching with shapeless coproducts?

import shapeless.{CNil, :+:}

type ListOrString = List[Int] :+: String :+: CNil

def f(a: ListOrString): Int = a match {
  case 0 :: second :: Nil => second
  case first :: Nil => first
  case Nil => -1
  case string: String => string.toInt
}

That of course doesn't work, since a is boxed as a Coproduct.

Is there an alternative way to use coproducts and maintain the ability to pattern match?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use the Inl and Inr constructors in the pattern match:

import shapeless.{ CNil, Inl, Inr, :+: }

type ListOrString = List[Int] :+: String :+: CNil

def f(a: ListOrString): Int = a match {
  case Inl(0 :: second :: Nil) => second
  case Inl(first :: Nil) => first
  case Inl(Nil) => -1
  case Inr(Inl(string)) => string.toInt
}

This approach isn't ideal because you have to handle the CNil case if you want the compiler to be able to tell that the match is exhaustive—we know that it's not possible for that case to match, but the compiler doesn't, so we have to do something like this:

def f(a: ListOrString): Int = a match {
  case Inl(0 :: second :: Nil) => second
  case Inl(first :: Nil) => first
  case Inl(Nil) => -1
  case Inl(other) => other.sum
  case Inr(Inl(string)) => string.toInt
  case Inr(Inr(_)) => sys.error("Impossible")
}

I also personally just find navigating to the appropriate positions in the coproduct with Inr and Inl a little counterintuitive.

In general it's better to fold over the coproduct with a polymorphic function value:

object losToInt extends shapeless.Poly1 {
  implicit val atList: Case.Aux[List[Int], Int] = at {
    case 0 :: second :: Nil => second
    case first :: Nil => first
    case Nil => -1
    case other => other.sum
  }

  implicit val atString: Case.Aux[String, Int] = at(_.toInt)
}

def f(a: ListOrString): Int = a.fold(losToInt)

Now the compiler will verify exhaustivity without you having to handle impossible cases.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...