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

在Scala中,如何避免强制转换函数参数?,scala,Scala,我希望有一个组件的各种“风格”,每个都处理不同的“线”格式(例如字符串、字节数组等)。下面的例子。read()函数的内部并不重要 请注意,在使用时,我需要将参数“Heavy”转换为thing.WIRE才能工作。由于这是我的顶级API,我不希望用户必须强制转换。他们在调用FantasticThing.apply(或接受默认值)时选择了口味。在那之后,我宁愿不需要演员阵容 如何避免强制转换并让Scala意识到read()参数是基于选择的StringFlavor的字符串 trait Flavor {

我希望有一个组件的各种“风格”,每个都处理不同的“线”格式(例如字符串、字节数组等)。下面的例子。read()函数的内部并不重要

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

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

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?
}

我冒昧地修改了您的代码,旨在说明(我所了解的)如何使用类型类类型参数而不是类型成员来解决您的问题

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
一般来说,最好不要使用类型成员,因为它们更复杂,并且用于更高级的东西,如路径依赖类型
如果您有任何疑问,请在评论中告诉我



免责声明:我不喜欢类型成员(仍在学习),这可能会促使我使用不同的替代方案。-在任何情况下,我希望您可以应用与实际问题类似的东西。

我冒昧地修改了您的代码,目的是展示如何(据我所知)使用类型类和类型参数来解决您的问题,而不是使用类型成员

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
一般来说,最好不要使用类型成员,因为它们更复杂,并且用于更高级的东西,如路径依赖类型
如果您有任何疑问,请在评论中告诉我



免责声明:我不喜欢类型成员(仍在学习),这可能会促使我使用不同的替代方案。-在任何情况下,我希望您可以应用与实际问题类似的东西。

您可以将
WIRE
作为类型参数,并通过类型成员或
生成器
类型传播它。即:

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
}

编辑:未将任何参数apply()添加到此anwser。如果使用了maker的默认值(例如StringMaker),则会出现编译错误,因为参数“Heavy”现在应该是maker.O类型。添加no arg apply可以解决此问题,同时为调用者提供相同的体验。

您可以将
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
}

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

可以将WIRE移动到类上的类型参数吗?另外,您应该避免到处强制转换,而不仅仅是在公共API中。问题是,在这里
def apply():Flavor=StringFlavor()
您正在丢失有关
WIRE
的所有类型信息。因此,Scala无法知道其底层类型是
String
,并且您的强制转换(一般来说)是不安全的您可以通过执行类似于
def apply():Flavor{type WIRE=String}=StringFlavor()
的操作来修复它,然后您可以安全地调用
fantasticsthing().read[Int](“Heavy”)
,因为编译器将保证该实例的
WIRE
等于
String
。现在,我不是把这个作为一个答案,因为我确信你真正的
应用
更复杂。。。想扩展吗?我看不到“参数”很重“在你的代码片段中的任何地方。WIRE
@Dima Greg在编辑我的评论时删除了该部分,我已重新编辑了该问题以包含缺少的部分。WIRE可以移动到类上的类型参数吗?另外,您应该避免到处强制转换,而不仅仅是在公共API中。问题是,在这里
def apply():Flavor=StringFlavor()
您正在丢失有关
WIRE
的所有类型信息。因此,Scala无法知道其底层类型是
String
,并且您的强制转换(一般来说)是不安全的您可以通过执行类似于
def apply():Flavor{type WIRE=String}=StringFlavor()
的操作来修复它,然后您可以安全地调用
fantasticsthing().read[Int](“Heavy”)
,因为编译器将保证该实例的
WIRE
等于
String
。现在,我不是把这个作为一个答案,因为我确信你真正的
应用
更复杂。。。想扩展吗?我看不到“参数”很重“在你的代码片段中的任何地方。WIRE
@Dima Greg在编辑我的评论时删除了这一部分,我重新编辑了这个问题以包含缺少的部分。我喜欢基于类型类的方法的优雅。一个限制是,如果我有其他字符串派生的类型,例如JSON或CSV。在这些情况下,编译器会抱怨其含蓄不明确。只要W的实际类型是不同的,这种方法就可以很好地工作。@Greg我明白了,我知道gogstad的答案是您需要的,但我只是想展示一下(只是为了记录在案,如果其他人有类似的问题),您可以使用包装器类使其与类型类一起工作。我喜欢基于类型类的方法的优雅。一个限制是,如果我有其他字符串派生的类型,例如JSON或CSV。在这些情况下,编译器会抱怨其含蓄不明确。只要实际