【问题标题】:Achieving Ad hoc polymorphism at function parameter level (mixing parameters of different type)在函数参数级别实现 Ad hoc 多态性(混合不同类型的参数)
【发布时间】:2020-12-02 10:24:32
【问题描述】:

当我在 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)

但是我不能调用toStringMyTypeAMyTypeB类型的混合参数:

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

是否可以重新定义toString,以便可以混合不同类型的参数(但仅适用于Show 类型类)?

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

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

【问题讨论】:

标签: scala polymorphism typeclass implicit scala-cats


【解决方案1】:

无形示例:

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 产生奇怪的冲突。


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

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

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

【讨论】:

    【解决方案2】:

    这是在 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)
    
    

    可以这样使用:

    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))
    

    Try it in Scastie

    【讨论】:

      【解决方案3】:

      paths的类型是什么?

      如果是List[T],那么范围内应该有一个隐含的Show[T]

      如果是List[Any],那么范围内应该有一个隐含的Show[Any]

      如果paths 包含不同类型的元素并且paths 不是List[Any],那么paths 根本不应该是List[...]。它可以是L &lt;: 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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-29
        • 2021-02-18
        • 1970-01-01
        • 2011-01-11
        • 2017-03-14
        相关资源
        最近更新 更多