Scala 3: typelevel - infering an output type from an input type

I have an decoder that uses implicit resolution to convert a type I to a type O (see code below, also available here). During conversion I do:

  val t1 = convertX[Int, Type1](1)

However, I would like to have that call use only one of the types (only one possible mapping):

  val t1 = convertX[Int](1)

Is this possible? Tried using typed lambdas but I cannot figure out what lambda should go were.

TIA

  trait DecoderX[I,O]:
    def decode(a: I): O 

  case class Type1(int: Int)
  case class Type2(str: String)

  object DecoderX:
    given DecoderX[Int, Type1] with
      def decode(x: Int) = Type1(x)

    given DecoderX[String, Type2] with
      def decode(x: String) = Type2(x)


  def convertX[I, O](in: I)(using DecoderX[I,O]): O = 
    val decoder = summon[DecoderX[I,O]]
    decoder.decode(in: I)

  import DecoderX.{*, given}

  val t1 = convertX[Int, Type1](1)

In Scala 2 you usually solve this problem using the partially applied trick, you can emulate the technique in Scala 3 like this: Scastie - An interactive playground for Scala.

However, I was under the impression that such a trick should not be needed anymore in Scala 3 thanks to features like context functions, and polymorphic functions… But I am not sure how exactly to join both to get the same effect.
It would be great if someone can show what would be the idiomatic way to solve this in Scala 3.

1 Like

If I understand you correctly, you want to specify the input type, as you do in your example, and there should only be one given Decoder instance per input type, correct?
For that, I’d suggest changing the Decoder trait to use a type member:

trait DecoderX[I]:
  type O
  def decode(a: I): O

object DecoderX:
  given DecoderX[Int] with
    type O = Type1
    def decode(x: Int) = Type1(x)

You can then define the convertX method as follows:

def convertX[I](in: I)(using decoder: DecoderX[I]): decoder.O =
  decoder.decode(in)

This will allow you to specify only the input type, or in case of your example even no type at all (1 can be inferred to Int):

val t1 = convertX(1)

If you want to specify the output type of the conversion instead, it is possible without changing the trait, but with a slightly different call-site signature:

def convertX[I](in: I) =
  [O] => (decoder: DecoderX[I,O]) ?=> decoder.decode(in)

// when calling, needs type *after* other parameter
val t1 = convertX(1)[Type1]

Both approaches in a Scastie

4 Likes

@crater2150 Nice. The version that uses the context function (convertX2 in the link) is what I am looking for (just as @BalmungSan had suggested). It provides the full type. Got to chew on this for a while :sunglasses:

Thanks to both.