在函数参数级别实现 Ad hoc 多态性(混合不同类型的参数)
Achieving Ad hoc polymorphism at function parameter level (mixing parameters of different type)
当我在 Scala 中有一个函数时:
def toString[T: Show](xs: T*): String = paths.map(_.show).mkString
以及以下类型 class 范围内的实例:
implicit val showA: Show[MyTypeA]
implicit val showB: Show[MyTypeB]
我可以通过以下方式使用函数 toString
:
val a1: MyTypeA
val a2: MyTypeA
val stringA = toString(a1, a2)
val b1: MyTypeB
val b2: MyTypeB
val stringB = toString(b1, b2)
但是我不能调用 toString
类型的混合参数 MyTypeA
和 MyTypeB
:
// doesn't compile, T is inferred to be of type Any
toString(a1, b1)
是否可以重新定义 toString
以便混合不同类型的参数(但只有 Show
类型 class 可用)?
请注意,我知道 cats show interpolator 可以解决这个特定示例,但我正在寻找一种也可以应用于不同情况的解决方案(例如 toNumber
)。
我也知道通过在将参数传递给 toString
函数之前调用 .show
来规避这个问题,但我正在寻找一种方法来避免这种情况,因为它会导致代码重复。
无形示例:
object myToString extends ProductArgs { //ProductArgs allows changing variable number of arguments to HList
//polymorphic function to iterate over values of HList and change to a string using Show instances
object showMapper extends Poly1 {
implicit def caseShow[V](implicit show: Show[V]): Case.Aux[V, String] = {
at[V](v => show.show(v))
}
}
def applyProduct[ARepr <: HList](
l: ARepr
)(
implicit mapper: Mapper[showMapper.type, ARepr]
): String = l.map(showMapper).mkString("", "", "")
}
现在让我们测试一下:
case class Test1(value: String)
case class Test2(value: String)
case class Test3(value: String)
implicit val show1: Show[Test1] = Show.show(_.value)
implicit val show2: Show[Test2] = Show.show(_.value)
println(myToString(Test1("a"), Test2("b"))) //"ab"
println(myToString(Test1("a"), Test2("b"), Test3("c"))) //won't compile since there's no instance of Show for Test3
顺便说一句,我认为 toString
不是最好的名字,因为它可能会导致与 java.lang.Object
中的 toString
发生奇怪的冲突。
如果你不想搞乱 shapeless,我想到的另一个解决方案是只创建具有不同 arity 的函数:
def toString[A: Show](a: A): String = ???
def toString[A: Show, B: Show](a: A, b: B): String = ???
//etc
这确实很麻烦,但它可能是解决您问题的最简单方法。
这是在 Dotty 中执行此操作的一种方法(请注意,此处使用的大多数 Dotty-specific 功能都不是必需的;它们只是为了让生活更轻松,但能够抽象出不同元数的元组是您在 Scala 2 中无法(轻松)做到的事情:
opaque type Show[T] = T => String
opaque type ShowTuple[T <: Tuple] = T => String
object ShowTuple {
given ShowTuple[EmptyTuple] = _ => ""
given showTuple[H, T <: Tuple](using show: Show[H], showTail: ShowTuple[T]) as ShowTuple[H *: T] =
{ case h *: t => show(h) + "," + showTail(t) }
}
def multiToString[T <: Tuple](t: T)(using showTuple: ShowTuple[T]) =
showTuple(t)
可以这样使用:
class TypeA(val i: Int)
class TypeB(val s: String)
class TypeC(val b: Boolean)
given Show[TypeA] = t => s"TypeA(${t.i})"
given Show[TypeB] = t => s"TypeB(${t.s})"
given Show[TypeC] = t => s"TypeC(${t.b})"
println(multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true))))
使用未给出隐式的类型失败:
class TypeD
multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true), new TypeD))
paths
的类型是什么?
如果是 List[T]
那么范围内应该有一个隐含的 Show[T]
。
如果是 List[Any]
那么范围内应该有一个隐含的 Show[Any]
。
如果 paths
包含不同类型的元素并且 paths
不是 List[Any]
,那么 paths
根本不应该是 List[...]
。它可以是 L <: HList
类型。你可以试试
import shapeless.{HList, HNil, Poly1, Poly2}
import shapeless.ops.hlist.{LeftReducer, Mapper}
trait Show[T] {
def show(t: T): String
}
implicit class ShowOps[T](t: T) {
def show(implicit s: Show[T]): String = s.show(t)
}
object show extends Poly1 {
implicit def cse[T: Show]: Case.Aux[T, String] = at(_.show)
}
object concat extends Poly2 {
implicit def cse: Case.Aux[String, String, String] = at(_ + _)
}
def toString[L <: HList, L1 <: HList](xs: L)(implicit
mapper: Mapper.Aux[show.type, L, L1],
reducer: LeftReducer.Aux[L1, concat.type, String]
): String = xs.map(show).reduceLeft(concat)
type MyTypeA
type MyTypeB
implicit val showA: Show[MyTypeA] = ???
implicit val showB: Show[MyTypeB] = ???
val a1: MyTypeA = ???
val b1: MyTypeB = ???
toString(a1 :: b1 :: HNil)
当我在 Scala 中有一个函数时:
def toString[T: Show](xs: T*): String = paths.map(_.show).mkString
以及以下类型 class 范围内的实例:
implicit val showA: Show[MyTypeA]
implicit val showB: Show[MyTypeB]
我可以通过以下方式使用函数 toString
:
val a1: MyTypeA
val a2: MyTypeA
val stringA = toString(a1, a2)
val b1: MyTypeB
val b2: MyTypeB
val stringB = toString(b1, b2)
但是我不能调用 toString
类型的混合参数 MyTypeA
和 MyTypeB
:
// doesn't compile, T is inferred to be of type Any
toString(a1, b1)
是否可以重新定义 toString
以便混合不同类型的参数(但只有 Show
类型 class 可用)?
请注意,我知道 cats show interpolator 可以解决这个特定示例,但我正在寻找一种也可以应用于不同情况的解决方案(例如 toNumber
)。
我也知道通过在将参数传递给 toString
函数之前调用 .show
来规避这个问题,但我正在寻找一种方法来避免这种情况,因为它会导致代码重复。
无形示例:
object myToString extends ProductArgs { //ProductArgs allows changing variable number of arguments to HList
//polymorphic function to iterate over values of HList and change to a string using Show instances
object showMapper extends Poly1 {
implicit def caseShow[V](implicit show: Show[V]): Case.Aux[V, String] = {
at[V](v => show.show(v))
}
}
def applyProduct[ARepr <: HList](
l: ARepr
)(
implicit mapper: Mapper[showMapper.type, ARepr]
): String = l.map(showMapper).mkString("", "", "")
}
现在让我们测试一下:
case class Test1(value: String)
case class Test2(value: String)
case class Test3(value: String)
implicit val show1: Show[Test1] = Show.show(_.value)
implicit val show2: Show[Test2] = Show.show(_.value)
println(myToString(Test1("a"), Test2("b"))) //"ab"
println(myToString(Test1("a"), Test2("b"), Test3("c"))) //won't compile since there's no instance of Show for Test3
顺便说一句,我认为 toString
不是最好的名字,因为它可能会导致与 java.lang.Object
中的 toString
发生奇怪的冲突。
如果你不想搞乱 shapeless,我想到的另一个解决方案是只创建具有不同 arity 的函数:
def toString[A: Show](a: A): String = ???
def toString[A: Show, B: Show](a: A, b: B): String = ???
//etc
这确实很麻烦,但它可能是解决您问题的最简单方法。
这是在 Dotty 中执行此操作的一种方法(请注意,此处使用的大多数 Dotty-specific 功能都不是必需的;它们只是为了让生活更轻松,但能够抽象出不同元数的元组是您在 Scala 2 中无法(轻松)做到的事情:
opaque type Show[T] = T => String
opaque type ShowTuple[T <: Tuple] = T => String
object ShowTuple {
given ShowTuple[EmptyTuple] = _ => ""
given showTuple[H, T <: Tuple](using show: Show[H], showTail: ShowTuple[T]) as ShowTuple[H *: T] =
{ case h *: t => show(h) + "," + showTail(t) }
}
def multiToString[T <: Tuple](t: T)(using showTuple: ShowTuple[T]) =
showTuple(t)
可以这样使用:
class TypeA(val i: Int)
class TypeB(val s: String)
class TypeC(val b: Boolean)
given Show[TypeA] = t => s"TypeA(${t.i})"
given Show[TypeB] = t => s"TypeB(${t.s})"
given Show[TypeC] = t => s"TypeC(${t.b})"
println(multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true))))
使用未给出隐式的类型失败:
class TypeD
multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true), new TypeD))
paths
的类型是什么?
如果是 List[T]
那么范围内应该有一个隐含的 Show[T]
。
如果是 List[Any]
那么范围内应该有一个隐含的 Show[Any]
。
如果 paths
包含不同类型的元素并且 paths
不是 List[Any]
,那么 paths
根本不应该是 List[...]
。它可以是 L <: HList
类型。你可以试试
import shapeless.{HList, HNil, Poly1, Poly2}
import shapeless.ops.hlist.{LeftReducer, Mapper}
trait Show[T] {
def show(t: T): String
}
implicit class ShowOps[T](t: T) {
def show(implicit s: Show[T]): String = s.show(t)
}
object show extends Poly1 {
implicit def cse[T: Show]: Case.Aux[T, String] = at(_.show)
}
object concat extends Poly2 {
implicit def cse: Case.Aux[String, String, String] = at(_ + _)
}
def toString[L <: HList, L1 <: HList](xs: L)(implicit
mapper: Mapper.Aux[show.type, L, L1],
reducer: LeftReducer.Aux[L1, concat.type, String]
): String = xs.map(show).reduceLeft(concat)
type MyTypeA
type MyTypeB
implicit val showA: Show[MyTypeA] = ???
implicit val showB: Show[MyTypeB] = ???
val a1: MyTypeA = ???
val b1: MyTypeB = ???
toString(a1 :: b1 :: HNil)