在Scala中,如何避免强制转换函数参数?

时间:2019-02-02 17:19:17

标签: scala

我希望组件具有各种“味道” ,每种都处理不同的“ wire” 格式(例如字符串,字节数组等)。下面的例子。 read()函数的内在并不重要。

请注意,使用时我需要将参数"Heavy"强制转换为thing.WIRE才能正常工作。由于这是我的顶级API,因此我不希望用户强制转换。他们在致电FantasticThing.apply时选择了口味(或接受默认值)。之后,我宁愿不需要演员。

如何避免强制转换并使Scala意识到read()参数是基于选择的StringFlavor String

trait Flavor {
  type WIRE
  def read[T](wire: WIRE)(implicit tt: TypeTag[T]): T
}

trait Maker {
  def make(): Flavor
}

object StringFlavor extends Maker {
  def make(): Flavor { type WIRE = String } = StringFlavor()
}

case class StringFlavor() extends Flavor {
  type WIRE = String
  def read[T](wire: String)(implicit tt: TypeTag[T]): T = {
    println(tt.tpe)
    if(tt.tpe =:= typeOf[Int]) {
      5.asInstanceOf[T]
    } else
      throw new Exception("Boom")
  }
}

object FantasticThing {
  def apply[WIRE](maker: Maker = StringFlavor): Flavor = maker.make()
}

object RunMe extends App {
  val thing: Flavor = FantasticThing(StringMaker)
  println(thing.read[Int]("Heavy".asInstanceOf[thing.WIRE])) // <-- How can I avoid this cast?
}
  • 根据Luis Miguel的注释进行编辑:我无法真正将该类型添加到FantasticThing.apply()中,否则我将失去可插入性。我希望用户轻松选择他们想要的口味。我已经进行了一些重构,以使用工厂模式进行显示,该模式确实添加了您建议的类型信息,但不幸的是,仍然需要强制转换顶级级别。

如果我提供了一堆调味料,那么用户应该可以执行以下操作:

val foo = FantasticThing(ByteArrayFlavor)

2 个答案:

答案 0 :(得分:1)

您可以将WIRE设为类型参数,并通过类型成员或您的Maker类型进行传播。即:

import scala.reflect.runtime.universe._

trait Flavor[WIRE] {
  def read[T](wire: WIRE)(implicit tt: TypeTag[T]): T
}

trait Maker {
  type O
  def make(): Flavor[O]
}

object StringMaker extends Maker {
  type O = String
  def make(): Flavor[O] = StringFlavor()
}

case class StringFlavor() extends Flavor[String] {
  def read[T](wire: String)(implicit tt: TypeTag[T]): T = {
    if(tt.tpe =:= typeOf[Int]) {
      5.asInstanceOf[T]
    } else
      throw new Exception("Boom")
  }
}

object FantasticThing {
  def apply(): Flavor[String] = StringMaker.make()
  def apply(maker: Maker): Flavor[maker.O]  = maker.make() // Path dependent type.
}

object RunMe extends App {
  val thing: Flavor[String] = FantasticThing(StringMaker)
  thing.read[Int]("Heavy") // res0: Int = 5
}

编辑:向此anwser添加了无参数的apply()。如果使用maker的默认值(例如StringMaker),则会出现编译错误,因为参数“ Heavy”现在应该为Maker.O类型。添加不带参数的应用解决了这个问题,同时提供给调用者相同的体验。

答案 1 :(得分:0)

我随意修改您的代码,目的是展示如何使用 typeclasses type参数解决(我的理解) ,而不是类型成员

import scala.reflect.runtime.universe.{TypeTag, typeOf}

implicit class Json(val underlying: String) extends AnyVal
implicit class Csv(val underlying: String) extends AnyVal

trait Flavor[W] {
  def read[T](wire: W)(implicit tt: TypeTag[T]): T
}

trait Maker[W] {
  def make(): Flavor[W]
}

object Maker {
  implicit val StringFlavorMaker: Maker[String] = new Maker[String] {
    override def make(): Flavor[String] = StringFlavor
  }

  implicit val JsonFlavorMaker: Maker[Json] = new Maker[Json] {
    override def make(): Flavor[Json] = JsonFlavor
  }

  implicit val CsvFlavorMaker: Maker[Csv] = new Maker[Csv] {
    override def make(): Flavor[Csv] = CsvFlavor
  }
}

case object StringFlavor extends Flavor[String] {
  override final def read[T](wire: String)(implicit tt: TypeTag[T]): T = {
    if(tt.tpe =:= typeOf[Int])
      0.asInstanceOf[T]
    else
      throw new Exception("Boom 1")
  }
}

case object JsonFlavor extends Flavor[Json] {
  override final def read[T](wire: Json)(implicit tt: TypeTag[T]): T = {
    if(tt.tpe =:= typeOf[Int])
      3.asInstanceOf[T]
    else
      throw new Exception("Boom 2")
  }
}

case object CsvFlavor extends Flavor[Csv] {
  override final def read[T](wire: Csv)(implicit tt: TypeTag[T]): T = {
    if(tt.tpe =:= typeOf[Int])
      5.asInstanceOf[T]
    else
      throw new Exception("Boom 3")
  }
} 

object FantasticThing {
  def apply[W](implicit maker: Maker[W]): Flavor[W] = maker.make()
}

然后可以创建和用户的任何风味(假定存在范围的隐式制造者)这种方式。

val stringFlavor = FantasticThing[String]
// stringFlavor: Flavor[String] = StringFlavor

stringFlavor.read[Int]("Heavy")
// res0: Int = 0

val jsonFlavor = FantasticThing[Json]
// jsonFlavor: Flavor[Json] = JsonFlavor

jsonFlavor.read[Int]("{'heavy':'true'}")
// res1: Int = 3

val csvFlavor = FantasticThing[Csv]
// csvFlavor: Flavor[Csv] = CsvFlavor

csvFlavor.read[Int]("Hea,vy")
// res2: Int = 0

在一般情况下,是更好地留的类型成员的关闭,因为它们是更复杂的,并用于例如路径依赖类型更多先进的东西。
如果您有任何疑问,请在评论中告诉我。


免责声明::我对类型成员不好(仍在学习中),这可能促使我使用其他替代方法。 -无论如何,我希望您可以应用与实际问题类似的东西。