Scala:以值为条件的隐式类型转换

问题描述 投票:0回答:1

任务是执行从字符串值到 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_]

我做错了什么?

scala pattern-matching typeclass implicit
1个回答
2
投票

类型类实例(隐式)在编译时解析。所以你必须在编译时知道一个数字是

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

Scala 如何根据特征派生类型类

使用类型类中的最低子类型?

密封特征中定义的案例对象的类型类实例

或者如果您在编译时真的知道

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
© www.soinside.com 2019 - 2024. All rights reserved.