任务是执行从字符串值到 Int 或 BigDecimal 的转换,取决于字符串值的长度。
例如,if stringValue > 10 => stringValue.toInt, else => BigDecimal(stringValue)
我正在尝试使用类型类:
trait Number
case class IntNumber(ch: String) extends Number
case class BigDecNumber(ch: String) extends Number
def wrap(ch: String): Number = ch match {
case c if (c.length <= 10) => IntNumber(c)
case c if (c.length > 10) => BigDecNumber(c)
}
trait TypeTransformer[A <: Number, B] {
def toDigit(ch: A): B
}
object transformer {
def transform[A <: Number, B](in: A)(implicit t: TypeTransformer[A, B]): B = t.toDigit(in)
implicit val stringToInt: TypeTransformer[IntNumber, Int] = (number: IntNumber) => number.ch.toInt
implicit val stringToBigDecimal: TypeTransformer[BigDecNumber, BigDecimal] = (number: BigDecNumber) => BigDecimal(number.ch)
}
但是,当我尝试应用此转换时:
transformer.transform(wrap("1231547234133123"))
我看到编译错误:No implicits found for parameter TypeTransformer[Number, B_]
我做错了什么?
类型类实例(隐式)在编译时解析。所以你必须在编译时知道一个数字是
IntNumber
还是BigDecNumber
,是c.length <= 10
还是c.length > 10
。
类型类是编译时的一种模式匹配。
如果你只在运行时知道是
c.length <= 10
还是 c.length > 10
那么你最好使用普通模式匹配
object transformer {
def transform(in: Number): Any = in match {
case num: IntNumber => num.ch.toInt
case num: BigDecNumber => BigDecimal(num.ch)
}
}
transformer.transform(wrap("1231547234133123")) // 1231547234133123
在 Scala 3 中,您可以将联合类型作为返回类型
https://docs.scala-lang.org/scala3/reference/new-types/union-types.html(Scala 3)
How to define "type disjunction" (union types)? (Scala 2)
object transformer:
def transform(in: Number): Int | BigDecimal = in match
case num: IntNumber => num.ch.toInt
case num: BigDecNumber => BigDecimal(num.ch)
或者,您可以为父类型定义类型类的实例
Number
object transformer {
def transform[A <: Number, B](in: A)(implicit t: TypeTransformer[A, B]): B = t.toDigit(in)
implicit val stringToInt: TypeTransformer[IntNumber, Int] = (number: IntNumber) => number.ch.toInt
implicit val stringToBigDecimal: TypeTransformer[BigDecNumber, BigDecimal] = (number: BigDecNumber) => BigDecimal(number.ch)
implicit val stringToAny: TypeTransformer[Number, Any /* Int | BigDecimal */] = {
case num: IntNumber => num.ch.toInt
case num: BigDecNumber => BigDecimal(num.ch)
}
}
import transformer._
transformer.transform(wrap("1231547234133123")) // 1231547234133123
顺便说一句,你最好将类型类实例放入伴随对象中,这样你就不必导入它们了。
您可以通过
TypeTransformer[Number, Any]
和TypeTransformer[Number, Int | BigDecimal]
来表达实例
TypeTransformer[IntNumber, Int]
(
TypeTransformer[BigDecNumber, BigDecimal]
)
implicit val stringToAny: TypeTransformer[Number, Any /* Int | BigDecimal */] = {
case num: IntNumber => implicitly[TypeTransformer[IntNumber, Int]].toDigit(num)
case num: BigDecNumber => implicitly[TypeTransformer[BigDecNumber, BigDecimal]].toDigit(num)
}
或者如果特征是密封的(以避免代码重复,例如,如果特征有很多继承者),例如使用Shapeless
sealed trait Number
case class IntNumber(ch: String) extends Number
case class BigDecNumber(ch: String) extends Number
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.{:+:, CNil, Coproduct, Generic}
trait TypeTransformer[A /*<: Number*/, B] {
def toDigit(ch: A): B
}
object TypeTransformer {
def transform[A, B](in: A)(implicit t: TypeTransformer[A, B]): B = t.toDigit(in)
implicit val stringToInt: TypeTransformer[IntNumber, Int] = (number: IntNumber) => number.ch.toInt
implicit val stringToBigDecimal: TypeTransformer[BigDecNumber, BigDecimal] = (number: BigDecNumber) => BigDecimal(number.ch)
implicit def gen[A, C <: Coproduct, Out](implicit
generic: Generic.Aux[A, C],
typeTransformer: TypeTransformer[C, Out]
): TypeTransformer[A, Out] =
a => typeTransformer.toDigit(generic.to(a))
implicit def coprod[H, T <: Coproduct, OutH <: Out, OutT <: Out, Out](implicit
headTypeTransformer: TypeTransformer[H, OutH],
tailTypeTransformer: TypeTransformer[T, OutT]
): TypeTransformer[H :+: T, Out] =
_.eliminate(headTypeTransformer.toDigit, tailTypeTransformer.toDigit)
implicit def single[H, Out](implicit
headTypeTransformer: TypeTransformer[H, Out]
): TypeTransformer[H :+: CNil, Out] =
_.eliminate(headTypeTransformer.toDigit, _.impossible)
}
TypeTransformer.transform[Number, Any](wrap("1231547234133123")) //1231547234133123
或者如果您在编译时真的知道
c.length <= 10
或 c.length > 10
那么您也可以使 wrap
成为类型类(使用 macros 或使用 singleton-ops)
// libraryDependencies += "eu.timepit" %% "singleton-ops" % "0.5.0"
import singleton.ops.{<=, >, Length, Require}
trait Wrap[S <: String with Singleton, Out <: Number] {
def toNumber(s: S): Out
}
object Wrap {
implicit def less10[S <: String with Singleton](implicit
require: Require[Length[S] <= 10]
): Wrap[S, IntNumber] = IntNumber(_)
implicit def more10[S <: String with Singleton](implicit
require: Require[Length[S] > 10]
): Wrap[S, BigDecNumber] = BigDecNumber(_)
}
def wrap[S <: String with Singleton, Out <: Number](s: S)(implicit w: Wrap[S, Out]): Out = w.toNumber(s)
trait TypeTransformer[A <: Number, B] {
def toDigit(ch: A): B
}
object TypeTransformer {
def transform[A <: Number, B](in: A)(implicit t: TypeTransformer[A, B]): B = t.toDigit(in)
implicit val stringToInt: TypeTransformer[IntNumber, Int] = (number: IntNumber) => number.ch.toInt
implicit val stringToBigDecimal: TypeTransformer[BigDecNumber, BigDecimal] = (number: BigDecNumber) => BigDecimal(number.ch)
}
TypeTransformer.transform(wrap("1231547234133123")) // 1231547234133123
val num = wrap("1231547234133123")
num: BigDecNumber // compiles
TypeTransformer.transform(num) // 1231547234133123
val num1: Number = wrap("1231547234133123")
TypeTransformer.transform(num1) // doesn't compile
在 Scala 3 中,您可以使用透明内联方法、编译时操作和匹配类型
import scala.compiletime.summonFrom
import scala.compiletime.ops.string.Length
import scala.compiletime.ops.int.<=
transparent inline def wrap[S <: String with Singleton](s: S): Number = summonFrom {
case _: (Length[S] <= 10) => IntNumber(s)
case _ => BigDecNumber(s)
}
type TypeTransformer[A <: Number] = A match
case IntNumber => Int
case BigDecNumber => BigDecimal
def transform[A <: Number](a: A): TypeTransformer[A] = a match
case num: IntNumber => num.ch.toInt
case num: BigDecNumber => BigDecimal(num.ch)
transform(wrap("1231547234133123")) // 1231547234133123
有时你真的必须在运行时解析类型类实例。在这种情况下,您可以使用运行时编译
def wrap(ch: String): Number = ch match {
case c if (c.length <= 10) => IntNumber(c)
case c if (c.length > 10) => BigDecNumber(c)
}
trait TypeTransformer[A <: Number, B] {
def toDigit(ch: A): B
}
object TypeTransformer {
def transform[A <: Number, B](in: A)(implicit t: TypeTransformer[A, B]): B = t.toDigit(in)
implicit val stringToInt: TypeTransformer[IntNumber, Int] = (number: IntNumber) => number.ch.toInt
implicit val stringToBigDecimal: TypeTransformer[BigDecNumber, BigDecimal] = (number: BigDecNumber) => BigDecimal(number.ch)
}
import scala.reflect.runtime
import runtime.universe._
import scala.tools.reflect.ToolBox // libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
val rm = runtime.currentMirror
val tb = rm.mkToolBox()
val num: Number = wrap("1231547234133123")
num.getClass // class BigDecNumber
TypeTransformer.transform(num)(
tb.eval(
tb.untypecheck(
tb.inferImplicitValue(
appliedType(
typeOf[TypeTransformer[_, _]].typeConstructor,
rm.classSymbol(num.getClass).toType,
WildcardType
),
silent = false
)
)
).asInstanceOf[TypeTransformer[Number, _]]
)
// 1231547234133123