0

Below I have a simplified version of a library I'm working on. I modeled this after the scala collection library's CanBuildFrom trait, since my requirements are similar. However, I can't quite get it to infer the correct builder to use in all cases. In the example below, all the methods work if I explicitly specify the builder to use, but I get a compile error for "ambiguous implicit values" when I don't specify the builder, even though it seems that the orderedMultiSignalBuilder should be strictly more specific than the multiSignalBuilder. What have I done wrong?

trait Builder[-F, -SourceElement[X] <: Element[X]] {
  type Result[X] <: MultiSignal[X]
  type TargetElement[X] >: SourceElement[X]
  def mapElement[T, U](element : SourceElement[T], value : U) : TargetElement[U] = {
    throw new Exception() //not implemented yet
  }
  def buildNew[T, U](element : TargetElement[T]) : Result[U] = {
    throw new Exception() //not implemented yet
  }
}

object Builder {
  implicit object multiSignalBuilder extends Builder[MultiSignal[Any], Element] {
    type Result[X] = MultiSignal[X]
  }
  implicit def orderedMultiSignalBuilder[P] = new Builder[OrderedMultiSignal[Any] { type Position = P }, ({type λ[α] = OrderedElement[α, P]})#λ]() {
    type Result[X] = OrderedMultiSignal[X] { type Position = P }
  }
}

trait Element[+T] {
  val value : T
}

trait OrderedElement[+T, +P] extends Element[T] {
}

trait MultiSignal[+T] {
  type ElementType[+X] <: Element[X]

  val element : ElementType[T]

  def map[U](f : T => U) (implicit builder : Builder[this.type, ElementType]) : builder.Result[U] =
    builder.buildNew(builder.mapElement(element, f(element.value)))
}

trait OrderedMultiSignal[+T] extends MultiSignal[T] {
  type Position
  type ElementType[+X] = OrderedElement[X, Position]
}

object multiSignal extends MultiSignal[Int] {
  type ElementType[+X] = Element[X]
  val element = new Element[Int] { val value = 0 }
}

object orderedMultiSignal extends OrderedMultiSignal[Int] {
  type Position = Int
  val element = new OrderedElement[Int, Int] { val value = 0 }
}

object Test {
  multiSignal.map(_.toString)
  orderedMultiSignal.map(_.toString) (Builder.multiSignalBuilder)
  val result : OrderedMultiSignal[String] { type Position = Int } = orderedMultiSignal.map(_.toString) (Builder.orderedMultiSignalBuilder[Int])

  //Next line gets compile error: ambiguous implicit values error
  val result2 : OrderedMultiSignal[String] { type Position = Int } = orderedMultiSignal.map(_.toString)

}
Nimrand
  • 1,748
  • 2
  • 16
  • 29

1 Answers1

2

Your two implicit builders in object Builder are not different in specificity from Scala's inference point of view. If you look at how builders are defined in collections, you'll notice that they are part of the companion objects of particular collection types. If you move your builders to the respective companion types, the prioritisation works:

trait Builder[-F, -Source[X] <: Element[X]] {
  type Result[X] <: MultiSignal[X]
}

trait Element[+T] {
  val value: T
}

trait OrderedElement[+T, +P] extends Element[T]

object MultiSignal {
  implicit object builder extends Builder[MultiSignal[Any], Element] {
    type Result[X] = MultiSignal[X]
  }
}
trait MultiSignal[+T] {
  type Elem[X] <: Element[X]

  def map[U](f: T => U)(implicit b: Builder[this.type, Elem]): b.Result[U] = ???
}

object OrderedMultiSignal {
  implicit def builder[P] =
    new Builder[OrderedMultiSignal[Any] { type Pos = P }, 
                ({type λ[α] = OrderedElement[α, P]})#λ]() {
    type Result[X] = OrderedMultiSignal[X] { type Pos = P }
  }
}
trait OrderedMultiSignal[+T] extends MultiSignal[T] {
  type Pos
  type Elem[X] = OrderedElement[X, Pos]
}

object multi extends MultiSignal[Int] {
  type Elem[X] = Element[X]
}

object ordered extends OrderedMultiSignal[Int] {
  type Pos = Int
}

object Test {
  multi  .map(_.toString)
  ordered.map(_.toString)

  val res0: MultiSignal       [String]                = multi  .map(_.toString)
  val res1: OrderedMultiSignal[String] {type Pos=Int} = ordered.map(_.toString)
}

Using this.type in map looks also wrong to me. It works here, but I think you should replace that somehow with a Repr that is another type member of the signal.

0__
  • 66,707
  • 21
  • 171
  • 266