user2916547
user2916547

Reputation: 2033

Type mismatch for function with Any* parameter

I have this method:

private def getMachineResponse(computeFunc: (Any*) => Option[List[MyThing]], any: Any*):  Route = {
  get {
    val things = computeFunc(any)

    // now do some stuff 
  }
}

I would like to call this method with two different computeFunc as parameters, one with 2 arguments and the other with 4 arguments. However the calls do not compile. I am trying this:

getMachineResponse(computeKnownItems, 1, "s")

where computeKnownItems is as follows:

private def computeKnownItems(mtype: Int, id: String): Option[List[MyThing]] = {
  // get the things
}

The error is "type mismatch, found (Int, String), required (Any*)".

Is there a way to achieve what I'm trying or should I make an overload for each type of computeFunc I need to pass in? (Like use (int, String) => Option[List[MyThing]] for computeFunc then create an overload that takes a computeFunc: (int, int, int, String) =>... etc)

Thank you.

Later edit: changed the parameter name to mtype (as it is in my actual code, I prettified it too much when posting here!)

Upvotes: 0

Views: 1762

Answers (1)

sarveshseri
sarveshseri

Reputation: 13985

First of all... I don't see this thing compiling. There are lots of problems other than co-variance and contra-variance issues.

Most obvious problem is here:

private def computeKnownItems(type: Int, id: String): Option[List[MyThing]] = {
  // get the things
}

No... you can not use type as an identifier... type is a keyword in Scala.

Other problem is that, something like this will not compile:

def simple( func: ( Int* ) => Int, ints: Int* ): Int = func( ints )

error: type mismatch;
  found   : Seq[Int]
  required: Int
    def func( ints: Int* ): Int = func( ints )

Neither will this:

def a( func: (Int*) => Int )( ints: Int* ): Int = func( ints )

error: type mismatch;
  found   : Seq[Int]
  required: Int
    def a( func: (Int*) => Int )( ints: Int* ): Int = func( ints )
                                                           ^

Nor will this:

scala> def fund( ints: Int* ): Int = ints.toList.sum
fund: (ints: Int*)Int

scala> def func( ints: Int* ): Int = fund( ints )
<console>:34: error: type mismatch;
  found   : Seq[Int]
  required: Int
    def func( ints: Int* ): Int = fund( ints )
                                        ^

The problem with all above was that the function was expecting multiple Int parameters, but was getting a single parameter which was a sequence ( *-parameters are implicitly received as Seq in function body ) . Correct way to pass vairable size parameters is following,

scala> def simple( func: ( Int* ) => Int, ints: Int* ): Int = func( ints: _* )
simple: (func: Int* => Int, ints: Int*)Int

_* is the special annotation that is usable only in arguments to *-parameter of a function. It makes any sequence to be passed as a *-parameter.

scala> val l = List( 4, 5, 6, 7, 3, 6, 3 )
l: List[Int] = List(4, 5, 6, 7, 3, 6, 3)    

scala> def pp( ints: Int* ) = println( ints )
pp: (ints: Int*)Unit

// All Int* is implicitly received as a Seq[ Int ]
// ( WrappedArray[ int ] in this case ) in function body.
scala> pp( 4, 5, 6 )
WrappedArray( 4, 5, 6 )

// Received as List[ Int ] in this case
scala> pp( l: _* )
List(4, 5, 6, 7, 3, 6, 3)

scala> def a( ints: Int* ) = ints.sum
a: (ints: Int*)Int

// passing a list wont work
scala> a( l )
<console>:11: error: type mismatch;
  found   : List[Int]
  required: Int
            a( l )
               ^

// list passed as multiple parameters
scala> a( l: _* )
res8: Int = 34

Now, coming to main issue... seems like you are have not considered two concepts known as co-variance and contra-variance.

While both of these are very general concepts... let me explain these in reference to types and classes.

Lets say, we have two types Animal and Horse where Horse is a sub-type of Animal or Horse <: Animal.

Now, Lets consider a famous type - List[ +T ]. Now why that + sign... Basically that + indicates that List[ T ] is co-variant for type T, which means that if Horse <: Animal then List[ Horse ] <: List[ Animal ]. Which in turn means that you can pass an instance of List[ Horse ] wherever an instance of List[ Animal ] is required. If you think about it, it kind of true even for English.

But, if you talk about functions, they are contra-variant, consider Function1[ -T, +R ] which means that if Horse <: Animal then Function1[ Animal, R ] is a subtype of Function1[ Horse, R ] or Function1[ Animal, R ] <: Function1[ Horse, R ].

In your case, your type (Any*) => Option[ List[ MyThing ] ] is contra-variant wrt. its arguments which means (int, String) => Option[ List[ MyThing ] ] is super-type of (Any*) => Option[ List[ MyThing ] ] which again means you can not use (int, String) => Option[ List[ MyThing ] ] in place of (Any*) => Option[ List[ MyThing ] ].

What, you can do is define functions for Map[ String, Any ]

def getMachineResponse(computeFunc: ( Map[ String, Any ] ) => Option[List[MyThing]], any: Map[ String, Any ] ):  Route = {
  get {
    val things = computeFunc(any)
    // now do some stuff 
  }
}

private def computeKnownItems(myMap: Map[ String, Any] ): Option[ List[ MyThing ] ] = {
  val myType = myMap.get( "type" ).asInstanceOf[ Int ]
  val myId = myMap.get( "id" ).asInstanceOf[ String ]
  // get the things
}

Upvotes: 0

Related Questions