3

I am new to Scala and I am stuck with a problem which I cannot find a solution online. So I ask for help here.

What I'm trying to do is to implement two map methods in class A. The two methods both accept a closure, but for one of them, the type of the return value of the closure is Tuple2. Also the return values of the two map methods are NOT of the same class (That's why I need two 'map' methods). The code is simplified as below:

object Test {
    class A[T] {
        def map[V](a: T => V): A[V] = {
            null
        }
        def map[K, V](a: T => Tuple2[K, V]): B[K, V] = {
            null
        }
    }

    class B[K, V] extends A[Tuple2[K, V]] {}

    def main(args: Array[String]) {
        new A[Int].map(x => x + 1) //compile error
        new A[Int].map{x => x + 1} //compile error
        new A[Int].map{x:Int => x + 1} //passed but ugly in use
    }
}

The definition of class A and B and the two map methods is acceptable by Scala on my computer. Here comes the problem. As I show in main method how I use the map methods in class A, the first two statements lead to compilation error (saying missing parameter type of x in the closure) and only the last one is executable.

And if I delete the second map method in class A, the first two statements become executable. I don't know why and how I should do. I just want to keep the two methods sharing the same name map and meanwhile I don't need to tell the type of parameters of the closure when using map methods.

Hope anyone interested in this question and offer me a better design. Thanks in advance.

Ben
  • 1,414
  • 2
  • 13
  • 18
Zhao Yunjian
  • 137
  • 9
  • Are you absolutely sure you want to overload `map` function ? What you ask is kind to hard to implement in scala. – vitalii Feb 24 '16 at 13:22
  • A couple of side points: (i) Scala has generics rather than templates - see [here](http://stackoverflow.com/a/498329/4070984) for a discussion of the difference. (ii) you're talking about methods rather than functions, which in Scala are importantly different (functions are objects, and can't be overloaded or generic) - see [here](http://stackoverflow.com/a/2530007/4070984). I've submitted an edit to correct these points. – Ben Feb 24 '16 at 15:02
  • @vitalii Yeah, I just want the `map` function to return different type in different situations (depending on the type of return value of the closure). If I can get an object of class `B` then I can be sure that the type of the value stored in class `B` is a `Tuple2` and I can also get the types of the key and the value. – Zhao Yunjian Feb 25 '16 at 02:57
  • @Ben As I've been using C++ for 4 to 5 years, I did have thought generics in Scala equals to templates in C++. Thanks for your correction. – Zhao Yunjian Feb 25 '16 at 03:04

2 Answers2

0

The compiler wants to determine the type of x, in order to do so, it must first determine which of the two map functions is used. However it cannot know which map function is used unless the type of the argument is known. Therefore the compiler cannot do this.

In this specific case, one could reason that the type of x would be Int regardless of which, map function is chosen, however, unfortunately the compiler cannot determine this.

What is possible, is explicitly specifying the type argument of the map function. This way the compiler knows which function to use (and what type arguments)

new A[Int].map[Int](x => x + 1)
new A[Int].map[Int, String]{x => (x, "1")}

Another alternative is to give the second map function a different name, since its signature is different from how map is usually defined.

Wellingr
  • 1,181
  • 1
  • 9
  • 19
  • Thanks but I don't want to show the type argument of the map function neither. I think vitalii's answer is what I want. – Zhao Yunjian Feb 25 '16 at 02:24
0

I don't know how to do this with method overloads. The problem is that type inferencer won't work in that case. You can take a look at magnet pattern but you will still have to specify complete types. Instead I have used an approach that is used in the scala collections. Mind you that there still might be some corner cases, I'm unaware of. Take a look:

object Example {
    class A[T] {
        def map[B, That](f: T => B)(implicit bf: CanBuildFrom[A[T], B, That]): That = {
            val res = f(???)
            bf(res)
        }

    }

    class B[K, V] extends A[Tuple2[K, V]] {}


    trait CanBuildFrom[-From, -Elem, +To] {
      def apply(el: Elem) : To
    }

    def main(args: Array[String]) {

        implicit def ev0[T,R] = new CanBuildFrom[A[T], R, A[R]] {
          def apply(el : R) : A[R] =  new A()
        }

        implicit def ev1[T,K,V] = new CanBuildFrom[A[T], Tuple2[K,V], B[K,V]] {
          def apply(el :(K,V)) : B[K,V] =  new B()
        }

        val res1 : B[Int, Int] = new A[Int].map(x => (2,3))
        val res2 : A[Int] = new A[Int].map { x => 1 }

        new A[Int].map { x => 1 }
        new A[Int].map(x => (2,3))

    }
}
vitalii
  • 3,335
  • 14
  • 18