Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Achieving Ad hoc polymorphism at function parameter level (mixing parameters of different type)

When I have a function in Scala:

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

And the following type class instances in scope:

implicit val showA: Show[MyTypeA]
implicit val showB: Show[MyTypeB]

I can use function toString in the following ways:

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

val b1: MyTypeB
val b2: MyTypeB
val stringB = toString(b1, b2)

But I cannot call toString mixing parameters of type MyTypeA and MyTypeB:

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

Is it possible to redefine toString in such a way that it becomes possible to mix parameters of different types (but only for which a Show typeclass is available)?

Note that I am aware of the cats show interpolator which solves this specific example, but I'm looking for a solution which can be applied to different cases as well (e.g. toNumber).

I am also aware of circumventing the problem by calling .show on the parameters before passing them to the toString function, but I'm looking for a way to avoid this as it results in code duplication.

like image 372
Arjun Dhawan Avatar asked Aug 12 '20 16:08

Arjun Dhawan


People also ask

Does ad hoc polymorphism allow methods having same name to act differently for different types?

The Ad-Hoc polymorphism is called as overloading. This allows function with same name to act in different manner for different types. The function and the operator both can be overloaded.

What is ad hoc polymorphism in C++?

Ad hoc polymorphism [Strachey67] in C++ is implemented using overloaded functions. Function overloading allows us to define two or more functions with the same name in the same scope [Wikipedia-4] . Overloaded functions are distinct and potentially heterogeneous implementations over a range of specific types.


2 Answers

Example with shapeless:

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("", "", "")
}

Now let's test it:

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

By the way, I think toString is not the best name, because probably it can cause weird conflicts with toString from java.lang.Object.


If you don't want to mess with shapeless, another solution that comes to my mind is to just create functions with different arity:

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

It's definitely cumbersome, but it might be the easiest way to solve your problem.

like image 95
Krzysztof Atłasik Avatar answered Nov 15 '22 05:11

Krzysztof Atłasik


Here's one way to do it in Dotty (note that most of the Dotty-specific features used here are not necessary; they're just to make life easier, but being able to abstract over tuples of different arities is something you can't do (easily) in 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)

It can be used like this:

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

Using a type for which an implicit is not given fails:

class TypeD

multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true), new TypeD))

Try it in Scastie

like image 37
user Avatar answered Nov 15 '22 04:11

user