我试图建立一个小的库来表示类型化路径。下面我有一个简化的版本,试图表达我想要的概念。
有没有办法让make
不发出错误?
package bar
object XX {
@FunctionalInterface
trait SegmentDecoder[A] {
self =>
def decode(segment: String): Either[String, A]
final def map[B](f: A => B): SegmentDecoder[B] = (s) => self.decode(s).map(f)
final def flatMap[B](f: A => SegmentDecoder[B]): SegmentDecoder[B] = (s) => self.decode(s).flatMap(a => f(a).decode(s))
}
object SegmentDecoder {
given SegmentDecoder[Int] = (seg) => seg.toIntOption.toRight(s"'$seg' is not a valid int")
given SegmentDecoder[Long] = (seg) => seg.toLongOption.toRight(s"'$seg' is not a valid long")
given SegmentDecoder[String] = (seg) => Right(seg)
given SegmentDecoder[Unit] = (_) => Right(())
}
case class Param[A](name: String, converter: SegmentDecoder[A])
def param[A](name: String)(using S: SegmentDecoder[A]) = Param(name, S)
enum P[A <: Tuple] {
def /(segment: String) = Static(this, segment)
def /[A](param: Param[A]) = Variable(this, param.name, param.converter)
case Root extends P[EmptyTuple]
case Static[T <: Tuple](parent: P[T], name: String) extends P[T]
case Variable[H, T <: Tuple](parent: P[T], name: String, decoder: SegmentDecoder[H]) extends P[Tuple.Append[T, H]]
}
type Elem[X <: Tuple] = X match {
case EmptyTuple => P.Root.type
case String *: xs => P.Static[xs]
case Param[y] *: xs => P.Variable[y, xs]
}
def makeBroken[T <: Tuple](t: T): Elem[T] = {
t match {
case _: EmptyTuple => P.Root
case (x *: xx): (String *: xs) => P.Static(makeBroken[xs](xx).asInstanceOf[P[xs]], x)
case (x *: xx): (Param[t] *: xs) => P.Variable(makeBroken[xs](xx).asInstanceOf[P[xs]], x.name, x.converter)
}
}
def make[T <: Tuple](t: T): P[T] = {
t match {
case _: EmptyTuple => P.Root.asInstanceOf[P[T]]
case (x: String) *: xx => P.Static(make(xx).asInstanceOf[P[xx.type]], x).asInstanceOf[P[T]]
case (x: Param[a]) *: xx => P.Variable(make(xx).asInstanceOf[P[xx.type]], x.name, x.converter).asInstanceOf[P[T]]
}
}
}
用法
object Main {
import XX.*
val link1 = make("foo", param[Int]("bar"), param[String]("bar"))
val link2 = P.Root / "foo" / param[Int]("bar") / param[String]("bar"))
//val link3 = makeBroken("foo", param[Int]("bar"), param[String]("bar")) //fails runtime with class cast exception
}
make
和makeBroken
导致警告
XX.scala:50:5: match may not be exhaustive.
It would fail on pattern case: _: *:[Any,Tuple]
[50:5]
XX.scala:58:5: match may not be exhaustive.
It would fail on pattern case: *:(_, _)
[58:5]
1条答案
按热度按时间68de4m5k1#
使用Scala编译器3.2.2版本编译代码时,我得到以下附加警告:
这解释了你得到的异常:编译器无法在运行时检查类型
(String *: xs)
。通常,由于类型擦除,Scala中的运行时类型检查无法区分参数化类型。因此,编译器生成的代码只检查t
是否是元组,然后直接将t.head
转换为String
,而不检查是否是元组。您可以通过打印生成的代码来查看它,例如使用scala -Xprint:genBCode so76236049.scala
:要避免这个缺点,并且仍然让
makeBroken
方法作为Elem[T]
进行类型检查,最好的办法是将匹配类型和术语级匹配分为两个级别: