在功能参数级别特定类型的混合参数上实现Ad hoc多态性

问题描述

当我在Scala中具有功能时:

def toString[T: Show](xs: T*): String = paths.map(_.show).mkString

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

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)

但是我无法调用toStringMyTypeA类型的MyTypeB混合参数:

// doesn't compile,T is inferred to be of type Any
toString(a1,b1)

是否可以通过重新定义toString的方式来混合不同类型的参数(但仅适用于Show类型类的参数)?

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

我也意识到可以通过在将参数传递给.show函数之前在参数上调用toString解决该问题,但是我正在寻找一种避免这种情况的方法,因为它会导致代码重复

解决方法

无形的示例:

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不是最好的名字,因为它可能会引起与toString中的java.lang.Object的怪异冲突。


如果您不想弄乱形状,我想到的另一种解决方案是只创建具有不同参数的函数:

def toString[A: Show](a: A): String = ???
def toString[A: Show,B: Show](a: A,b: B): String = ???
//etc

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

,

这是在Dotty中完成此操作的一种方法(请注意,此处使用的大多数Dotty特定功能不是必需的;它们只是为了使生活更轻松,但是您可以抽象出不同Arity元组) '在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 TypeC(true),new TypeD))

Try it in Scastie

,

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)