Understanding Tuple.Map and polymorphic function types

I was looking at Tuple and I’m trying to understand how to use that.
Looks like a polymorphic function type to me.

If I do (1, “test”, true).map([t] => (x: t) => ???) I can do very generic things, e.g., wrap the values in some type constructor like Option(x)
But it would be great to actually match on the type so I can do something more useful with the element. For example:

(1, “test”, true).map(\[t\] => (x: t) => x match
  case s: String => s.toLowerCase
  case other => other
)

This ofc. does not compile. I could coerce the type, e.g., x.toLowerCase.asInstanceOf(t), which is nasty.

I guess the type is erased and I can’t really achieve what I want in a type safe way using this?
Is there a better way to achieve what I want?

1 Like

Your two basic choices are to use a typeclass or a match type.

A match type will work if the set of types you need to operate on is closed and known in advance:

scala> type Flip[T] = T match
         case String => Int
         case Int => String
                                                                                                    
scala> def flip[T](t: T): Flip[T] = t match
         case s: String => s.length
         case i: Int => i.toString
       
def flip[T](t: T): Flip[T]
                                                                                                    
scala> ("two", 3, "four").map([T] => (t: T) => flip(t))
val res0: (Int, String, Int) = (3, "3", 4)

It’s unfortunate one can’t simply .map(flip), because the language doesn’t currently offer eta-expansion to polymorphic function types, as per Polymorphic function parameter types not inferred · Issue #6892 · scala/scala3 · GitHub

If you want to allow arbitrary types to be added later, then you can do the same thing with a typeclass, declaring an individual typeclass instance for each input type you want to support.

3 Likes

I was not able to make it work with typeclasses:

trait Showable[T]:
  extension (x: T)
    def show(): String


given Showable[Int] = new {
  extension (x: Int)
    def show(): String = x.toString
}

given Showable[String] = new {
  extension (x: String)
    def show(): String = '"' + x + '"'
}

def show[T : Showable as T](x: T): String = x.show()

("two", 3, "four").map([T] => (t: T) => show(t)) // No given instance of type worksheet.Showable[T] was found for parameter T of method show in object worksheet

And I had to fight to get an understandable error message ^^’

Here is my original attempt:

trait Flippable[T]:
  type Out
  extension (x: T)
    def flip(): Out


// Sadly netiher work as 1: the compiler doesn't infer `Out`, and 2: extension methods apparently don't work for SAM types:
// given Flippable[Int] = _.toString
// given Flippable[Int] = (x: Int) => x.toString

given Flippable[Int] = new {
  type Out = String
  extension (x: Int)
    def flip(): Out = x.toString
}

given Flippable[String] = new {
  type Out = Int
  extension (x: String)
    def flip(): Out = x.length
}

def flip[T : Flippable as T](x: T): T.Out = x.flip()

("two", 3, "four").map([T] => (t: T) => flip(t)) // No given instance of type worksheet.Flippable[T] was found for parameter T of method flip in object worksheet
1 Like

Oh interesting, was I too hasty there? I’d like to take a second look this week…