SOLVED: Combined use of implicit conversion and implicit class failing

Hello,

I would like to know if it is possible to use both an implicit
class and implicit conversion together. In the example below
I would like to call an operator on a tuple but have that tuple
first be converted to an anonymous class.

Unfortunately I get:

value andNext is not a member of (String, Int => Int)

Anyone know how to get this working (or confirm that it is not possible).

TIA

see https://scastie.scala-lang.org/drmVmEO7TeWl1E7M6lDyqA

object Test {
  import scala.language.implicitConversions
  
  trait NamedFunc[I,O]{
    def name:String
    def func(i:I): O
  }

  implicit def toNamedFunc[I,O](named_func: (String, I => O)): NamedFunc[I,O] = {
    val nm = named_func._1
    val f = named_func._2
    new NamedFunc[I,O] {
      override val name: String = nm
      override def func(b:I): O = {
        f(b)
      }
    }
  }

  implicit class FuncOps[I1,O1](o: NamedFunc[I1,O1]) {
    def -:[O2,P2](that: NamedFunc[O1,O2]): NamedFunc[I1,O2] = {
      new NamedFunc[I1,O2] {
        override val name: String = o.name + " ->: " + that.name
        override def func(b:I1): O2 = {
          val first = o.func(b)
          that.func(first)
        }
      }
    }

    def andNext[O2,P2](that: NamedFunc[O1,O2]): NamedFunc[I1,O2] = that -: o
  }
  

  def f(a:Int):Int = -a
  val h: Int => Int = (a:Int) => 2*a

  val g: (List[Int], Int) => Int =
    (a:List[Int], b:Int) => {
      if (a.isEmpty)
        b
      else
        a.head * b
    }
  
  def main(args: Array[String]): Unit = {
    
    val n1:NamedFunc[Int,Int] = ("f", f _)
    val n2:NamedFunc[Int,Int] = ("h", h)
    
    val itn1_2 = n1 -: n2
    
    //val itt1_2 = ("f", f _) -: ("g", g, List(3))
    val itt1_2 = ("f", f _) andNext ("g", g, List(3))
  }

}

If you want to have a syntax extension for everything that can be converted to a certain type, you can do something like this:

implicit class FuncOps[T,I1,O1](t: T)(implicit conv: T => NamedFunc[I1,O1]) {
  private val o = conv(t)
  def -:[O2,P2](that: NamedFunc[O1,O2]): NamedFunc[I1,O2] = {
    new NamedFunc[I1,O2] {
      override val name: String = o.name + " ->: " + that.name
      override def func(b:I1): O2 = {
        val first = o.func(b)
        that.func(first)
      }
    }
  }

  def andNext[O2,P2](that: NamedFunc[O1,O2]): NamedFunc[I1,O2] = that -: o
}

The compiler will not automatically chain implicit conversions.

@Jasper-M Thank you. Once again.

Unfortunately I cannot get it to work on 2.12.6. I have also tried
2.11.x and 2.12.x in Scastie but compilation fails with these errors:

value -: is not a member of (String, Int => Double)
value andNext is not a member of (String, Int => Int)

In Dotty this works without a problem. Initially I thought
this may be due to the left/right associativity but both
methods seem to fail.

Can anyone give me ideas on how to get implicit
conversion working in this example?

TIA.

PS: The initial code was not correct in several places (due to right
associativity of the operator), so I now use this code for testing:

object Test {
  import scala.language.implicitConversions

  trait NamedFunc[I,O]{
    def name:String
    def func(i:I): O
  }

  implicit def toNamedFunc[I,O](named_func: (String, I => O)): NamedFunc[I,O] = {
    val nm = named_func._1
    val f = named_func._2
    new NamedFunc[I,O] {
      override val name: String = nm
      override def func(b:I): O = {
        f(b)
      }
    }
  }

  implicit class FuncOps[T,IO,O](t: T)(implicit conv: T => NamedFunc[IO,O]) {
    private val o = conv(t)
    def -:[I](that: NamedFunc[I,IO]): NamedFunc[I,O] = {
      new NamedFunc[I,O] {
        override val name: String = that.name + " ->: " + o.name
        override def func(b:I): O = {
          val first:IO = that.func(b)
          o.func(first)
        }
      }
    }

    def andNext[I](that: NamedFunc[O,I]): NamedFunc[IO,I] = {
      new NamedFunc[IO,I] {
        override val name: String = o.name + " andNext " + that.name
        override def func(b:IO): I = {
          val first:O = o.func(b)
          that.func(first)
        }
      }
    }
  }



  def f(a:Int):Int = -a
  val h: Int => Double = (a:Int) => (2*a).toDouble

  def main(args: Array[String]): Unit = {

    val n1:NamedFunc[Int,Int] = ("f", f _)
    val n2:NamedFunc[Int,Double] = ("h", h)

    // n2 is assigned implicit class member t (right associative)
    val itn1_2 = n1 -: n2
    println(itn1_2.name)
    println(itn1_2.func(2))

    // ("h", h) is assigned implicit class member t (right associative)
    val itt1_2 = ("f", f _) -: ("h", h)
    println(itt1_2.name)
    println(itt1_2.func(2))
    
    // ("f", f _) is assigned implicit class member t (left associative)
    val ist1_2 = ("f", f _) andNext ("h", h)
    println(ist1_2.name)
    println(ist1_2.func(2))
  }

}

Have a solution. Not what I really want because in my use case
I will need more than one FuncOps but seems to work for both
scalac (2.13, 2.12) and Dotty.

Had to specify the implicit in FuncOps to match the tuple.
If anyone has a better (more general) solution, please tell me.

object Test {
  import scala.language.implicitConversions

  trait NamedFunc[I,O]{ self =>
    def name:String
    def func(i:I): O

    def -:[IN](that: NamedFunc[IN,I]): NamedFunc[IN,O] = {
      new NamedFunc[IN,O] {
        override val name: String = that.name + " ->: " + self.name
        override def func(b:IN): O = {
          val first:I = that.func(b)
          self.func(first)
        }
      }
    }

    def andNext[Out](that: NamedFunc[O,Out]): NamedFunc[I,Out] = {
      new NamedFunc[I,Out] {
        override val name: String = self.name + " andNext " + that.name
        override def func(b:I): Out = {
          val first:O = self.func(b)
          that.func(first)
        }
      }
    }
  }

  implicit def toNamedFunc[I,O](named_func: (String, I => O)): NamedFunc[I,O] = {
    val nm = named_func._1
    val f = named_func._2
    new NamedFunc[I,O] {
      override val name: String = nm
      override def func(b:I): O = {
        f(b)
      }
    }
  }

  implicit class FuncOps[A,B,IO,O](t: (A,B))(implicit conv: ((A,B)) => NamedFunc[IO,O]) {
    private val o = conv(t)
    def -:[I](that: NamedFunc[I,IO]): NamedFunc[I,O] = that.andNext(o)
    def andNext[I](that: NamedFunc[O,I]): NamedFunc[IO,I] = o.andNext(that)
  }



  def f(a:Int):Int = -a
  val h: Int => Double = (a:Int) => (2*a).toDouble

  def main(args: Array[String]): Unit = {

    val n1:NamedFunc[Int,Int] = ("f", f _)
    val n2:NamedFunc[Int,Double] = ("h", h)

    // n2 is assigned implicit class member t (right associative)
    val itn1_2 = n1 -: n2
    println(itn1_2.name)
    println(itn1_2.func(2))

    // ("h", h) is assigned implicit class member t (right associative)
    val itt1_2 = ("f", f _) -: ("h", h)
    println(itt1_2.name)
    println(itt1_2.func(2))
    
    // ("f", f _) is assigned implicit class member t (left associative)
    val ist1_2 = ("f", f _) andNext ("h", h)
    println(ist1_2.name)
    println(ist1_2.func(2))
  }

}