简体   繁体   中英

How can I create an inline function in scala that respects implicit conversions?

Take the following code as an example:

object Test6 {
  def main(args: Array[String]): Unit = {
    val a = new A
    //works
    takesBTuple(tupleMaker(a, a))
    //does not work, because a becomes an instance of ArrowAssoc[A] instead of ArrowAssoc[B]
    takesBTuple(a->a)
  }

  class A

  class B

  implicit def atob(a: A): B = new B

  def tupleMaker[X, Y](x: X, y: Y): (X, Y) = (x, y)

  def takesBTuple(b: (B, B)): Unit = println("taken")
}

How can I get the behavior of TupleMaker (specially w.r.t. implicit conversions), but with an inline function? I can modify both class A and B, and use any infix operator (doesn't have to be an existing one) if that helps the solution.

You can add one more conversion

implicit def tupleToTuple(a: (A, A)): (B, B) = (atob(a._1), atob(a._2))

More generic solution is

class A
class A1
class B

implicit def atob(a: A): B = new B
implicit def a1tob(a: A1): B = new B

implicit def tupleToTuple[T, X1, Y1, X, Y](t: (X, Y))(implicit 
  ev: T <:< (X1, Y1), 
  ev1: X => X1, 
  ev2: Y => Y1
): T = (t._1, t._2)


val a = new A
val a1 = new A1

takesBTuple(a -> a) // compiles
takesBTuple(a1 -> a1) // compiles
takesBTuple(a -> a1) // compiles 

Simpler

implicit def tupleToTuple[X, Y, X1, Y1](t: (X, Y))(implicit 
  ev1: X => X1, 
  ev2: Y => Y1
): (X1, Y1) = (t._1, t._2) 

doesn't work because X is inferred first (to be A ). So ev: T <:< (X1, Y1) is a trick to make X1 , Y1 be inferred first (to be B ). Implicits are resolved from the left to the right.

The easy way to fix that would be

takesBTuple((a:B) -> a)

Otherwise, you can define a trait ConvertibleToB that has -> as a method, and then this would work.

val a = new A
val aa = new AA
//all of these work
takesBTuple(tupleMaker(a, a))
takesBTuple(a -> a)
takesBTuple(aa -> a)
takesBTuple(a -> aa)
takesConvertibleToBTuple(a -> a)
takesConvertibleToBTuple(a -> aa)

//Just for demonstration
def takesConvertibleToBTuple(t: (ConvertibleToB, ConvertibleToB)): Unit = println("took " + t)
trait ConvertibleToB {
  def convertToB: B
  def ->(b: B): (B, B) = (convertToB, b)
  def ->(a: this.type): (this.type, this.type) = (this, a)
}

class A extends ConvertibleToB {
  override def convertToB: B = new B
}

class AA extends ConvertibleToB {
  override def convertToB: B = new B
}

implicit def makeB(c: ConvertibleToB): B = c.convertToB

The only implicit def you need to import is makeB . I don't know if this is what you want or if it will be practical for you, though, especially if you don't want tuples of type (A, AA) to be converted to (B, B)

Link to Scastie: https://scastie.scala-lang.org/pPuzw0sSQfKlglfT0a3Rrw

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM