在函数参数级别实现 Ad hoc 多态性(混合不同类型的参数)

Arj*_*wan 5 polymorphism scala implicit typeclass scala-cats

当我在 Scala 中有一个函数时:

def toString[T: Show](xs: T*): String = paths.map(_.show).mkString
Run Code Online (Sandbox Code Playgroud)

以及范围内的以下类型类实例:

implicit val showA: Show[MyTypeA]
implicit val showB: Show[MyTypeB]
Run Code Online (Sandbox Code Playgroud)

我可以通过toString以下方式使用函数:

val a1: MyTypeA
val a2: MyTypeA
val stringA = toString(a1, a2)

val b1: MyTypeB
val b2: MyTypeB
val stringB = toString(b1, b2)
Run Code Online (Sandbox Code Playgroud)

但我不能叫toString混合型的参数MyTypeAMyTypeB

// doesn't compile, T is inferred to be of type Any
toString(a1, b1)
Run Code Online (Sandbox Code Playgroud)

是否有可能以toString一种可以混合不同类型的参数(但仅限于Show类型类可用)的方式重新定义?

请注意,我知道 cat show interpolator 可以解决这个特定的例子,但我正在寻找一种也可以应用于不同情况的解决方案(例如toNumber)。

我也知道通过.show在将参数传递给toString函数之前调用参数来规避这个问题,但我正在寻找一种方法来避免这种情况,因为它会导致代码重复。

Krz*_*sik 6

无形的例子:

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("", "", "")
}
Run Code Online (Sandbox Code Playgroud)

现在让我们测试一下:

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

Run Code Online (Sandbox Code Playgroud)

顺便说一句,我认为toString这不是最好的名字,因为它可能会导致与toStringfrom 的奇怪冲突java.lang.Object


如果您不想弄乱无形,我想到的另一个解决方案是创建具有不同数量的函数:

def toString[A: Show](a: A): String = ???
def toString[A: Show, B: Show](a: A, b: B): String = ???
//etc
Run Code Online (Sandbox Code Playgroud)

这绝对很麻烦,但它可能是解决问题的最简单方法。


use*_*ser 5

这是在 Dotty 中执行此操作的一种方法(请注意,此处使用的大多数 Dotty 特定功能不是必需的;它们只是为了让生活更轻松,但是能够对不同参数的元组进行抽象是您无法做到的(很容易)在 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)

Run Code Online (Sandbox Code Playgroud)

它可以像这样使用:

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))))
Run Code Online (Sandbox Code Playgroud)

使用未给出隐式的类型失败:

class TypeD

multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true), new TypeD))
Run Code Online (Sandbox Code Playgroud)

在 Scastie 试试