Class dependent type with implicits 2.13.16

Hi! I read a bit about the class dependent types, aux pattern etc., but I do not understand why this minified example does not work with an implicit conversion. Is it possible to make it working? The solution may require macros.

import scala.language.implicitConversions


trait Block[T] {
  type Result

  def apply(bi: T): Result
}

object Block {
  implicit def conv[T, R](f: T => R): Block[T] {type Result = R} = new Block[T] {
    override type Result = R

    def apply(value: T): Result = f(value)
  }
}

object Main {

  import Block.conv

  def doSth[T](block: Block[T]): block.Result = ???

  def main(args: Array[String]): Unit = {
    val good: Int = doSth[Int](conv(_ + 5)) // ok
    val bad: Int = doSth[Int](_ + 5)
    //type mismatch;
    // found   : Int
    // required: Block[Int]#Result
    //    val bad: Int = doSth[Int](_ + 5)
  }
}

Of course, Block[T, R] with def doSth[T, R] would do the job, but I care not to give the result type as a type parameter.
I use Scala 2.13.16 and the typer result is:

❯ ./mill testing.compile
[44/44] testing.compile
[44] [info] compiling 1 Scala source to /Users/bkozak/IdeaProjects/macro3-z-bartusiem/out/testing/compile.dest/classes ...
[44] |-- <empty> APPSELmode-EXPRmode-POLYmode-QUALmode (site: package <root>) 
[44] |    \-> <empty>.type
[44] |-- scala.language APPSELmode-EXPRmode-POLYmode-QUALmode (site: package <empty>) 
[44] |    |-- scala APPSELmode-EXPRmode-POLYmode-QUALmode (site: package <empty>) 
[44] |    |    \-> scala.type
[44] |    \-> language.type
[44] |-- class Block[T] BYVALmode-EXPRmode (site: package <empty>) 
[44] |    |-- Result BYVALmode-EXPRmode (site: trait Block) 
[44] |    |    \-> [type Result] Block.this.Result
[44] |    |-- def apply BYVALmode-EXPRmode (site: trait Block) 
[44] |    |    |-- Result TYPEmode (site: method apply in Block) 
[44] |    |    |    \-> Block.this.Result
[44] |    |    |-- T TYPEmode (site: value bi in Block) 
[44] |    |    |    \-> T
[44] |    |    \-> [def apply] (bi: T): Block.this.Result
[44] |    \-> [trait Block] Block[T]
[44] |-- object Block BYVALmode-EXPRmode (site: package <empty>) 
[44] |    |-- super APPSELmode-EXPRmode-POLYmode-QUALmode (silent: <init> in Block) 
[44] |    |    |-- this EXPRmode (silent: <init> in Block) 
[44] |    |    |    \-> Block.type
[44] |    |    \-> Block.type
[44] |    |-- def conv[T, R] BYVALmode-EXPRmode (site: object Block) 
[44] |    |    |-- Block[T] { type Result = R } TYPEmode (site: method conv in Block) 
[44] |    |    |    |-- Block[T] TYPEmode (site: method conv in Block) 
[44] |    |    |    |    |-- T TYPEmode (site: method conv in Block) 
[44] |    |    |    |    |    \-> T
[44] |    |    |    |    \-> Block[T]
[44] |    |    |    |-- R TYPEmode (site: type Result in <refinement>) 
[44] |    |    |    |    \-> R
[44] |    |    |    [adapt] Block[T] { type Result = R } is now a TypeTree(Block[T]{type Result = R})
[44] |    |    |    \-> Block[T]{type Result = R}
[44] |    |    |-- _root_.scala.Function1[T, R] TYPEmode (site: value f in Block) 
[44] |    |    |    |-- T TYPEmode (site: value f in Block) 
[44] |    |    |    |    \-> T
[44] |    |    |    |-- R TYPEmode (site: value f in Block) 
[44] |    |    |    |    \-> R
[44] |    |    |    \-> T => R
[44] |    |    |-- { final class $anon extends Block[T] { def <init>() = { s... : pt=Block[T]{type Result = R} EXPRmode (site: method conv in Block) 
[44] |    |    |    |-- Block FUNmode-TYPEmode (site: anonymous class $anon) 
[44] |    |    |    |    \-> Block
[44] |    |    |    |-- Block[T] TYPEmode (site: anonymous class $anon) 
[44] |    |    |    |    |-- T TYPEmode (site: anonymous class $anon) 
[44] |    |    |    |    |    \-> T
[44] |    |    |    |    \-> Block[T]
[44] |    |    |    |-- Result TYPEmode (site: method apply in $anon) 
[44] |    |    |    |    |-- R TYPEmode (site: type Result in $anon) 
[44] |    |    |    |    |    \-> R
[44] |    |    |    |    [adapt] R is now a TypeTree(this.Result)
[44] |    |    |    |    \-> this.Result
[44] |    |    |    |-- T TYPEmode (site: value value in $anon) 
[44] |    |    |    |    \-> T
[44] |    |    |    |-- class $anon BYVALmode-EXPRmode (site: method conv in Block) 
[44] |    |    |    |    |-- [T]AnyRef { type Result def apply(bi: T): Block.this.Resu... TYPEmode (site: anonymous class $anon) 
[44] |    |    |    |    |    \-> Block[T]
[44] |    |    |    |    |-- super APPSELmode-EXPRmode-POLYmode-QUALmode (silent: <init> in $anon) 
[44] |    |    |    |    |    |-- this EXPRmode (silent: <init> in $anon) 
[44] |    |    |    |    |    |    \-> Block[T]
[44] |    |    |    |    |    \-> super.type (with underlying type AnyRef)
[44] |    |    |    |    |-- Result BYVALmode-EXPRmode (site: anonymous class $anon) 
[44] |    |    |    |    |    \-> [type Result] this.Result
[44] |    |    |    |    |-- def apply BYVALmode-EXPRmode (site: anonymous class $anon) 
[44] |    |    |    |    |    |-- f(value) : pt=this.Result EXPRmode (site: method apply in $anon) 
[44] |    |    |    |    |    |    |-- f APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method apply in $anon) 
[44] |    |    |    |    |    |    |    |-- f.apply APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method apply in $anon) 
[44] |    |    |    |    |    |    |    |    \-> (v1: T): R
[44] |    |    |    |    |    |    |    [adapt] T => R adapted to (v1: T1): R
[44] |    |    |    |    |    |    |    \-> (v1: T): R
[44] |    |    |    |    |    |    |-- value : pt=T BYVALmode-EXPRmode (silent: method apply in $anon) 
[44] |    |    |    |    |    |    |    \-> T
[44] |    |    |    |    |    |    \-> R
[44] |    |    |    |    |    \-> [def apply] (value: T): this.Result
[44] |    |    |    |    \-> [class $anon] Block[T]
[44] |    |    |    |-- new $anon() : pt=Block[T]{type Result = R} EXPRmode (site: method conv in Block) 
[44] |    |    |    |    |-- new $anon APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method conv in Block) 
[44] |    |    |    |    |    |-- new $anon APPSELmode-EXPRmode-POLYmode-QUALmode (silent: method conv in Block) 
[44] |    |    |    |    |    |    |-- $anon FUNmode-TYPEmode (silent: method conv in Block) 
[44] |    |    |    |    |    |    |    \-> Block[T]
[44] |    |    |    |    |    |    \-> Block[T]
[44] |    |    |    |    |    \-> (): Block[T]
[44] |    |    |    |    \-> Block[T]
[44] |    |    |    \-> Block[T]
[44] |    |    \-> [def conv] [T, R](f: T => R): Block[T]{type Result = R}
[44] |    \-> [object Block] Block.type
[44] |-- object Main BYVALmode-EXPRmode (site: package <empty>) 
[44] |    |-- super APPSELmode-EXPRmode-POLYmode-QUALmode (silent: <init> in Main) 
[44] |    |    |-- this EXPRmode (silent: <init> in Main) 
[44] |    |    |    \-> Main.type
[44] |    |    \-> Main.type
[44] |    |-- Block APPSELmode-EXPRmode-POLYmode-QUALmode (site: object Main) 
[44] |    |    \-> Block.type
[44] |    |-- def doSth[T] BYVALmode-EXPRmode (site: object Main) 
[44] |    |    |-- block.Result TYPEmode (site: method doSth in Main) 
[44] |    |    |    |-- block APPSELmode-EXPRmode-QUALmode (site: method doSth in Main) implicits disabled
[44] |    |    |    |    |-- Block[T] TYPEmode (site: value block in Main) implicits disabled
[44] |    |    |    |    |    |-- T TYPEmode (site: value block in Main) implicits disabled
[44] |    |    |    |    |    |    \-> T
[44] |    |    |    |    |    \-> Block[T]
[44] |    |    |    |    \-> block.type (with underlying type Block[T])
[44] |    |    |    \-> block.Result
[44] |    |    |-- $qmark$qmark$qmark : pt=block.Result EXPRmode (site: method doSth in Main) 
[44] |    |    |    \-> Nothing
[44] |    |    \-> [def doSth] [T](block: Block[T]): block.Result
[44] |    |-- def main BYVALmode-EXPRmode (site: object Main) 
[44] |    |    |-- Unit TYPEmode (site: method main in Main) 
[44] |    |    |    \-> Unit
[44] |    |    |-- Array[String] TYPEmode (site: value args in Main) 
[44] |    |    |    |-- String TYPEmode (site: value args in Main) 
[44] |    |    |    |    [adapt] String is now a TypeTree(String)
[44] |    |    |    |    \-> String
[44] |    |    |    \-> Array[String]
[44] |    |    |-- { val good: Int = doSth[Int](conv(((x$1) => x$1.$plus(5))... : pt=Unit EXPRmode (site: method main in Main) 
[44] |    |    |    |-- <?> BYVALmode-EXPRmode (site: method main in Main) 
[44] |    |    |    |    |-- Int TYPEmode (site: value good in Main) 
[44] |    |    |    |    |    \-> Int
[44] |    |    |    |    |-- doSth[Int](conv(((x$1) => x$1.$plus(5)))) : pt=Int BYVALmode-EXPRmode (site: value good in Main) 
[44] |    |    |    |    |    |-- doSth[Int] APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value good in Main) 
[44] |    |    |    |    |    |    |-- doSth APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode-TAPPmode (silent: value good in Main) 
[44] |    |    |    |    |    |    |    \-> [T](block: Block[T]): block.Result
[44] |    |    |    |    |    |    |-- Int TYPEmode (silent: value good in Main) 
[44] |    |    |    |    |    |    |    \-> Int
[44] |    |    |    |    |    |    \-> (block: Block[Int]): block.Result
[44] |    |    |    |    |    |-- conv(((x$1) => x$1.$plus(5))) : pt=Block[Int] BYVALmode-EXPRmode (site: value good in Main) 
[44] |    |    |    |    |    |    |-- conv APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value good in Main) 
[44] |    |    |    |    |    |    |    [adapt] [T, R](f: T => R): Block[T]{type Result = R} adapted to [T, R](f: T => R): Block[T]{type Result = R}
[44] |    |    |    |    |    |    |    \-> (f: T => R): Block[T]{type Result = R}
[44] |    |    |    |    |    |    |-- ((x$1) => x$1.$plus(5)) : pt=Int => ? BYVALmode-EXPRmode-POLYmode (site: value good in Main) 
[44] |    |    |    |    |    |    |    |-- x$1.$plus(5) EXPRmode (site: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    |-- x$1.$plus APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    |    |-- x$1 APPSELmode-EXPRmode-POLYmode-QUALmode (silent: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    |    |    \-> x$1.type (with underlying type Int)
[44] |    |    |    |    |    |    |    |    |    \-> (x: Double): Double <and> (x: Float): Float <and> (x: Long): Long <and> (x: Int): Int <and> (x: Char): Int <and> (x: Short): Int <and> (x: Byte): Int <and> (x: String): String
[44] |    |    |    |    |    |    |    |    |-- 5 BYVALmode-EXPRmode (silent: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    |    \-> Int(5)
[44] |    |    |    |    |    |    |    |    \-> Int
[44] |    |    |    |    |    |    |    \-> Int => Int
[44] |    |    |    |    |    |    solving for (T: ?T, R: ?R)
[44] |    |    |    |    |    |    \-> Block[Int]{type Result = Int}
[44] |    |    |    |    |    \-> Int
[44] |    |    |    |    \-> [val good] Int
[44] |    |    |    |-- <?> BYVALmode-EXPRmode (site: method main in Main) 
[44] |    |    |    |    |-- Int TYPEmode (site: value bad in Main) 
[44] |    |    |    |    |    \-> Int
[44] |    |    |    |    |-- doSth[Int](((x$2) => x$2.$plus(5))) : pt=Int BYVALmode-EXPRmode (site: value bad in Main) 
[44] |    |    |    |    |    |-- doSth[Int] APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value bad in Main) 
[44] |    |    |    |    |    |    |-- doSth APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode-TAPPmode (silent: value bad in Main) 
[44] |    |    |    |    |    |    |    \-> [T](block: Block[T]): block.Result
[44] |    |    |    |    |    |    |-- Int TYPEmode (silent: value bad in Main) 
[44] |    |    |    |    |    |    |    \-> Int
[44] |    |    |    |    |    |    \-> (block: Block[Int]): block.Result
[44] |    |    |    |    |    |-- ((x$2) => x$2.$plus(5)) : pt=Block[Int] BYVALmode-EXPRmode (site: value bad in Main) 
[44] |    |    |    |    |    |    |-- x$2.$plus(5) : pt=Block[Int]#Result EXPRmode (site: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |-- x$2.$plus APPSELmode-BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    |-- x$2 APPSELmode-EXPRmode-POLYmode-QUALmode (silent: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    |    \-> x$2.type (with underlying type Int)
[44] |    |    |    |    |    |    |    |    \-> (x: Double): Double <and> (x: Float): Float <and> (x: Long): Long <and> (x: Int): Int <and> (x: Char): Int <and> (x: Short): Int <and> (x: Byte): Int <and> (x: String): String
[44] |    |    |    |    |    |    |    [search #1] start `<notype>`, searching for adaptation to pt=Double => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    3 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #2] start `<notype>`, searching for adaptation to pt=(=> Double) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    3 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #3] start `<notype>`, searching for adaptation to pt=Float => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    4 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #4] start `<notype>`, searching for adaptation to pt=(=> Float) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    4 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #5] start `<notype>`, searching for adaptation to pt=Long => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    4 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #6] start `<notype>`, searching for adaptation to pt=(=> Long) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    4 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #7] start `<notype>`, searching for adaptation to pt=Int => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    3 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #8] start `<notype>`, searching for adaptation to pt=(=> Int) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    3 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #9] start `<notype>`, searching for adaptation to pt=Int => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #10] start `<notype>`, searching for adaptation to pt=(=> Int) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #11] start `<notype>`, searching for adaptation to pt=Int => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #12] start `<notype>`, searching for adaptation to pt=(=> Int) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #13] start `<notype>`, searching for adaptation to pt=Int => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #14] start `<notype>`, searching for adaptation to pt=(=> Int) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #15] start `<notype>`, searching for adaptation to pt=String => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    3 implicits in companion scope
[44] |    |    |    |    |    |    |    [search #16] start `<notype>`, searching for adaptation to pt=(=> String) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    3 implicits in companion scope
[44] |    |    |    |    |    |    |    |-- 5 BYVALmode-EXPRmode (silent: value $anonfun in Main) 
[44] |    |    |    |    |    |    |    |    \-> Int(5)
[44] |    |    |    |    |    |    |    [search #17] start `(x: Int): Int`, searching for adaptation to pt=Int => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] |    |    |    |    |    |    |    [search #18] start `(x: Int): Int`, searching for adaptation to pt=(=> Int) => Block[Int]#Result (silent: value $anonfun in Main) implicits disabled
[44] [error] /Users/bkozak/IdeaProjects/macro3-z-bartusiem/testing/src/Main.scala:26:33: type mismatch;
[44] [error]  found   : Int
[44] [error]  required: Block[Int]#Result
[44] [error]     val bad: Int = doSth[Int](_ + 5)
[44] [error]                                 ^
[44] |    |    |    |    |    |    |    \-> <error>
[44] |    |    |    |    |    |    \-> <error>
[44] |    |    |    |    |    \-> <error>
[44] |    |    |    |    \-> [val bad] Int
[44] |    |    |    \-> Unit
[44] |    |    \-> [def main] (args: Array[String]): Unit
[44] |    \-> [object Main] Main.type
[44] |-- Result BYVALmode-EXPRmode (site: <refinement>) 
[44] |    \-> [type Result] this.Result
[44] [adapt] implicitConversions adapted to languageFeature.implicitConversions based on pt scala.languageFeature.implicitConversions
[44] [error] one error found

@halotukozak Try

object Block {
  type Aux[T, R] = Block[T] { type Result = R }

  implicit def conv[T, R](f: T => R): Aux[T, R] = ...
}
def doSth[T] = new PartiallyAppliedDoSth[T]
class PartiallyAppliedDoSth[T] {
  def apply[R](block: Block.Aux[T, R]): R = ???
}
1 Like

omg, it’s even better, cause I do not need the implicit conversion now. I knew the AUX pattern, but couldn’t make it useful :sweat_smile:.
Thanks!

This is Scala 3 version - I kept the auxiliary helper conv so you can still call it explicitly, but the
actual implicit conversion from a plain function T => R to a
Block.Aux[T, R] is now expressed with given Conversion … and the
PartiallyAppliedDoSth class receives, through a using, whatever
Numeric[T] happens to be in scope so that it can manufacture a sensible
T value (its zero) to feed to the block.

import scala.language.implicitConversions
import scala.math.Numeric
import scala.Conversion

object DependentTypeConv {

  trait Block[T] {
    type Result
    def apply(bi: T): Result
  }

  type Aux[T, R] = Block[T] { type Result = R }

  object Block {

    def conv[T, R](f: T => R): Aux[T, R] =
      new Block[T] {
        type Result = R
        def apply(value: T): Result = f(value)
      }

    given functionToBlock[T, R]: Conversion[T => R, Aux[T, R]] with
      def apply(f: T => R): Aux[T, R] = conv(f)
  }

  import Block.conv
  import Block.given

  def doSth[T](using Numeric[T]) = new PartiallyAppliedDoSth[T]

  class PartiallyAppliedDoSth[T](using n: Numeric[T]) {
    def apply[R](block: Aux[T, R]): R = block(n.zero)
  }

  def main(args: Array[String]): Unit = {
    val good: Int = doSth[Int](conv(_ + 5))                // explicit helper
    val bad : Int = doSth[Int](_ + 5)                      // uses the given Conversion
    println(good)                                          // 5
    println(bad)                                           // 5
  }
}