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