Question

As far as I know there is no shared trait in the collections library that defines the map method (most likely because there are different signatures for map).

I have an observable value (think of a property in a ui system) that has a change event. The observable values can be mapped using a map method.

When we are however working with a type that already has a map method, we should be able to use the built-in method of map.

So instead of:

prop map { x => 
  x map { actualX =>
   //do something
  }
}

I want to use it like this:

prop map { actualX =>
  //do something
}

I have a simplified test case. First the different parts I am using:

// leaving out the observable part
trait ObservableValue[T] {
  def value: T
}

trait LowerPriorityImplicits {
  // default implementation that adds a regular map method
  implicit class RichObservableValue1[A](o: ObservableValue[A]) {
    def map[B](f: A => B): ObservableValue[B] = new ObservableValue[B] {
      def value = f(o.value)
    }
  }
}

object ObservableValue extends LowerPriorityImplicits {

  // describe a type that has a map method
  type HasMapMethod[A, Container[X]] = {
    def map[B](f: A => B): Container[B]
  }

  // complex implementation that uses the builtin map if present
  implicit class RichObservableValue2[A, Container[Z] <: HasMapMethod[Z, Container]](
      o: ObservableValue[Container[A]]) {

    def map[B](f: A => B): ObservableValue[Container[B]] = 
      new ObservableValue[Container[B]] {
        def value = o.value.map(f)
      }
  }
}

If there is something (or maybe a lot) wrong with the above code let me know. I want to use it like this:

class TestCase extends ObservableValue[Option[Int]] {
  def value = None
}

val x = new TestCase

x map { value =>
  // this fails because the compiler finds the lower priority implicit
  (value: Int).toString
}

// the method itself works fine
ObservableValue.RichObservableValue2(x) map { value =>
  (value: Int).toString
}

If I change Container[B] into Any it will find the RichObservableValue2 implicit conversion.

My knowledge about how types are used to select implicits is limited.

I tried to find the answer in the following locations, but the subject is a bit overwhelming:

Is there anyway to solve this challenge?

Edit

I know about the FilterMonadic trait for collections. I am looking for a solution that recognizes the map method as defined in the Option class.

Edit 2

It seems the FilterMonadic variant is not working either. I added RichObservableValue3 to the RichObservableValue object.

implicit class RichObservableValue3[A, C[Z] <: FilterMonadic[Z, C[Z]]](o: ObservableValue[C[A]]) {

  def map[B, That](f: A => B)(implicit bf: CanBuildFrom[C[A], B, That]): ObservableValue[That] = new ObservableValue[That] {
    def value = o.value.map(f)

    val change = o.change map (_ map f)
  }
}

And again that implicit conversion is not chosen although a List[Int] is a valid argument. I must be missing some rule that is used when selecting implicits.

Was it helpful?

Solution

I finally found the part that I was missing. In order for the implicit to match I had to add the following type parameter:

T[Dummy <: Container[_]] <: ObservableValue[Dummy]

The resulting object:

object ObservableValue {

  implicit class Mappable[A](o: ObservableValue[A]) {

    def map[B](f: A => B): ObservableValue[B] = new ObservableValue[B] {
      def value = f(o.value)
    }
  }

  // FMC = FilterMonadicContainer
  // D = Dummy
  implicit class FilterMonadicMappable[A, FMC[D] <: FilterMonadic[D, FMC[D]]](o: ObservableValue[FMC[A]]) {

    def map[B, That](f: A => B)(implicit bf: CanBuildFrom[FMC[A], B, That]): ObservableValue[That] = new ObservableValue[That] {
      def value = o.value.map(f)
    }
  }

  type HasMap[A, That[_]] = {
    def map[B](f: A => B): That[B]
  }

  // OVC = ObservableValueContainer
  // MC = MappableContainer
  // D = Dummy
  implicit class SimpleMappable[A, MC[D] <: HasMap[D, MC], OVC[D <: MC[_]] <: ObservableValue[D]](o: OVC[MC[A]]) {

    def map[B](f: A => B): ObservableValue[MC[B]] = new ObservableValue[MC[B]] {
      def value = o.value.map(f)
    }
  }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top