type inference in argument list in combination with setter not working - scala

Let's imagine the following items in scope:
object Thing {
var data: Box[String] = Empty
}
def perform[T](setter: Box[T] => Unit) {
// doesn't matter
}
The following fails to compile:
perform(Thing.data = _)
The error message is:
<console>:12: error: missing parameter type for expanded function ((x$1) => Thing.data = x$1)
perform(Thing.data = _)
^
<console>:12: warning: a type was inferred to be `Any`; this may indicate a programming error.
perform(Thing.data = _)
^
While the following compiles:
perform(Thing.data_=)
I have since surpassed this issue by creating a better abstraction, but my curiosity still remains.
Can anyone explain why this is?

Let's expand out what you're doing in the first example:
Thing.data = _
is shorthand for defining an anonymous function, which looks like:
def anon[T](x: Box[T]) {
Thing.data = x
}
So when you call
perform(Thing.data = _)
it's the same as
perform(anon)
The problem is anon and perform take a type parameter T and at no point are you declaring what T is. The compiler can only infer type parameters in a function call from passed arguments, not from within the function body, so it cannot infer in anon that T should be String.
Notice that if you call
perform[String](Thing.data = _)
the compiler has no issue because it now knows what T should be, and if you try to use any type besides string, you'll get a type mismatch error, but the error occurs in the body of the anonymous function, not on the call to perform.
However, when you call
perform(Thing.data_=)
you are passing the method Thing.data_=, which is explicitly defined as Box[String] => Unit, so the compiler can infer perform's type parameter because it is coming from a function argument.

Related

Mysterious GADT skolem: What type is trying to escape its scope?

Scala 11.2 is giving me this error:
error: type mismatch;
found : Seq[Some[V]]
required: Seq[Option[?V8]] where type ?V8 <: V (this is a GADT skolem)
val output = f(ivs.map(iv => Some(iv.get._1)))
^
First off, this seems like a strange error message: Doesn't Seq[Some[V]] conform to Seq[Option[V]]?
Here are the parts of the surrounding code that seem relevant:
def evalDependencyTree[V]
(linkRelevance: LinkInfo => Option[LinkStrength])
(dtree: DependencyTree[V, LinkInfo], strengthSoFar: LinkStrength = 1.0)
: Option[(V, LinkStrength)] = dtree match {
. . .
case DFunction(f, inputs) => {
val ivs = inputs.map { input =>
evalDependencyTree(linkRelevance)(input, strengthSoFar) // <-- Recursive call
}
val output = f(ivs.map(iv => Some(iv.get._1))) // <-- The line with the error
. . .
}
}
trait DependencyTree[+V, +L]
case class DFunction[V, L](
f: Seq[Option[V]] => Option[V], inputs: Seq[DependencyTree[V, L]])
extends DependencyTree[V, L]
My (very limited) understanding of GADT skolems is that they're types defined by the compiler during type inference, which copy an existing type argument in order to prevent that type from "escaping" its scope, as in a recursive call—that is, to prevent its being referred to from a wider scope that has no access to the type.
I don't see how V could refer to different types in different scopes here. The recursive call to evalDependencyTree has the same type argument, V, as the current call to evalDependencyTree. I tried explicitly writing evalDependencyTree[V] for the recursive call, but the compiler returned the same error message. This code did work when evalDependencyTree did not have a type argument; in that version, dtree was hard-coded to DependencyTree[Int, LinkInfo].
What type is trying to escape? Or rather, what am I doing wrong?
I found a workaround myself: explicitly spell out the full type of f in the pattern-match, like this:
case DFunction(f: Seq[Option[V]] => Option[V], inputs) => . . .
This works, but I'm not accepting it as an answer because I don't have an explanation of why it's necessary. I still don't know when to expect this kind of error or what causes it. If you know, please post an answer!
Also, I would have thought that most of the type explicitly provided for f would have been lost by type erasure. So, there are two important things I can't explain about this workaround.

Removing ambiguity of overloaded method definition

The compiler is getting confused by an overloaded method definiton and I cannot find a way to clarify my intention sufficiently.
I have the following code:
val part: (QueryParams) => List[ResultMap] = (partOf.find _).andThen(makeMap)
The find method is overloaded:
def find(params: QueryParams): List[U] = ...
def find(params: QueryParams, flattener: U => T): List[T] = ...
The code was working fine as long as there was a single definiton of find. Since I had to add the second find definiton with 2 params, the compiler is generating this error:
Error:(15, 31) ambiguous reference to overloaded definition,
both method find in trait DataFetcher of type (params: ...QueryParams)List[...Parser]
and method find in trait DataFetcher of type (params: ...QueryParams, flattener: ...Parser => ...ResultTuple)List[...ResultTuple]
match expected type ?
val part: Fetcher = (partOf.find _).andThen(makeMap)
^
IMHO there is no ambiguity. The type of part is defined to accept one argument of type QueryParams. There is only one method accepting a single QueryParams. U and T are different types and makeMap expects a List[U] There are no implicits, default values or varargs involved.
Is there a way to further clarify my intention to the compiler?
EDIT: one way to remove the ambiguity is to introduce an intermediary value, clarifying the expected type of the eta expansion:
val find: (QueryParams) => List[ResultTuple] = partOf.find _
val part: (QueryParams) => List[ResultMap] = find andThen makeMap
But since makeMap is only accepting List[ResultTuple] I stil dont get the reason for the supposed ambiguity and would prefer not to introduce the extra value. Any clarification?
First, it is important to understand that the trailing underscore is a deliberate design decision to prevent programmer mistakes. The only exception is when the type is explicitly declared.
Here is an example illustrating this point.
object A {
def add(a: Int)(b:Int): Int = a + b
val x: Int => Int = add(5) // compiles fine
val y = add(5) // produces a compiler error
}
The same applies to your question. Because you do not specify the intermediate type, when you write find _, should the compiler infer the type to be QueryParams => List[ResultTuple] (as you may expect) or should it be (QueryParams, U => T) => List[ResultTuple]? Note that the trailing underscore does not stand for a single argument, it just lifts the method to a function. When the type if declared, you can drop the trailing underscore and write find where you would have written find _.
I see from your edit that you found out that an intermediate value with a declared type works. Another (slightly clunky) way to clarify your intent is the following.
val part: (QueryParams) => List[ResultMap] = (x => partOf.find(x)).andThen(makeMap)

Right associative functions with two parameter list

I was looking at the FoldLeft and FoldRight methods and the operator version of the method was extremely peculiar which was something like this (0 /: List.range(1,10))(+).
For right associative functions with two parameter lists one would expect the syntax to be something like this((param1)(param2) op HostClass).
But here in this case it is of the syntax (param1 op HostClass)(param2). This causes ambiguity with another case where a right associative function returns another function that takes a single parameter.
Because of this ambiguity the class compiles but fails when the function call is made as shown below.
class Test() {
val func1:(String => String) = { (in) => in * 2 }
def `test:`(x:String) = { println(x); func1 }
def `test:`(x:String)(y:String) = { x+" "+y }
}
val test = new Test
(("Foo") `test:` test)("hello")
<console>:10: error: ambiguous reference to overloaded definition,
both method test: in class Test of type (x: String)(y: String)String
and method test: in class Test of type (x: String)String => String
match argument types (String)
(("Foo") `test:` test)("hello")
so my questions are
Is this an expected behaviour or is it a bug?
Why the two parameter list right associative function call has been designed the way it is, instead of what I think to be more intuitive syntax of ((param1)(param2) op HostClass)?
Is there a workaround to call either of the overloaded test: function without ambiguity.
The Scala's Type System considers only the first parameter list of the function for type inference. Hence to uniquely identify one of the overloaded method in a class or object the first parameter list of the method has to be distinct for each of the overloaded definition. This can be demonstrated by the following example.
object Test {
def test(x:String)(y:Int) = { x+" "+y.toString() }
def test(x:String)(y:String) = { x+" "+y }
}
Test.test("Hello")(1)
<console>:9: error: ambiguous reference to overloaded definition,
both method test in object Test of type (x: String)(y: String)String
and method test in object Test of type (x: String)(y: Int)String
match argument types (String)
Test.test("Hello")(1)
Does it really fail at runtime? When I tested it, the class compiles, but the call of the method test: does not.
I think that the problem is not with the operator syntax, but with the fact that you have two overloaded functions, one with just one and the other with two parameter lists.
You will get the same error with the dot-notation:
test.`test:`("Foo")("hello")
If you rename the one-param list function, the ambiguity will be gone and
(("Foo") `test:` test)("hello")
will compile.

In Scala, why can't I explicitly use a parameter type here?

The codes below works well
List("ios","android","wm").exists(x =>"ios ok".contains(x))
However, if I add the parameter type in the anonymous function like this, it complains type mismatch:
scala> List("ios","android","wm").exists(x: String => "ios ok".contains(x))
<console>:1: error: identifier expected but string literal found.
List("ios","android","wm").exists(x: String => "ios ok".contains(x))
^
If I use _ instead of x, it doesn't compile either:
scala>List("ios","android","wm").exists(_ =>"ios ok".contains(_))
<console>:8: error: missing parameter type for expanded function ((x$2) => "ios ok".<contains: error>(x$2))
Does anyone have ideas about this?
Is there any implicit type conversion happening in these codes?
And how could I use the parameter type explicityly here?
I'm thinking when the compiler sees :String => ... it may be looking to complete a function type like String => A. This is getting triggered by the parentheses, because you'd normally have a typed anonymous function within curly braces.
These work:
List("ios","android","wm").exists((x: String) => x.contains(x))
List("ios","android","wm").exists { x: String => x.contains(x) }
And this last bit doesn't make any sense:
List("ios","android","wm").exists(_ =>"ios ok".contains(_))
The first _ means you don't care what the element is, which is obviously not the case. So the compiler is looking to apply a function with two arguments with an argument list of one.
You want this instead:
List("ios","android","wm").exists("ios ok".contains(_))

class A has one type parameter, but type B has one

Recently I stumbled across a strange (to me) compiler error message. Consider the following code:
trait Foo {
type Res <: Foo
type Bar[X <: Res]
}
class MyFoo extends Foo {
override type Res = MyFoo
override type Bar[X <: Res] = List[X]
}
type FOO[F <: Foo, R <: Foo, B[_ <: R]] = F { type Res = R;
type Bar[X <: R] = B[X] }
def process[F <: Foo, R <: Foo, B[_ <: R]](f: FOO[F, R, B]) {}
Now, if I want to call the process method I have to explicitly write the type parameters:
process[MyFoo, MyFoo, List](new MyFoo) // fine
If I write:
process(new MyFoo)
or
process((new MyFoo): FOO[MyFoo, MyFoo, List])
I get the following error message:
inferred kinds of the type arguments (MyFoo,MyFoo,List[X]) do not conform to the expected kinds of the type parameters (type F,type R,type B). List[X]'s type parameters do not match type B's expected parameters: class List has one type parameter, but type B has one
Why isn´t the compiler able to infer the types (although I explicitly stated them at call parameter)? And what does that class List has one type parameter, but type B has one mean? Something has one, but the other has also one, and that´s why they don´t fit together???
If we look to the Scala compiler, the sources could help us understanding what the problem is. I have never contributed to the Scala compiler, but I found the sources very readable and I have already investigated on that.
The class responsible for type inference is scala.tools.nsctypechecker.Infer which you can find simply by looking in the Scala compiler sources for a part of your error. You'll find out the following fragment:
/** error if arguments not within bounds. */
def checkBounds(pos: Position, pre: Type, owner: Symbol,
tparams: List[Symbol], targs: List[Type], prefix: String) = {
//#M validate variances & bounds of targs wrt variances & bounds of tparams
//#M TODO: better place to check this?
//#M TODO: errors for getters & setters are reported separately
val kindErrors = checkKindBounds(tparams, targs, pre, owner)
if(!kindErrors.isEmpty) {
error(pos,
prefix + "kinds of the type arguments " + targs.mkString("(", ",", ")") +
" do not conform to the expected kinds of the type parameters "+ tparams.mkString("(", ",", ")") + tparams.head.locationString+ "." +
kindErrors.toList.mkString("\n", ", ", ""))
}
So now the point is understanding why checkKindBounds(tparams, targs, pre, owner) returns those errors. If you go down the method call chain, you will see that the checkKindBounds call another method
val errors = checkKindBounds0(tparams, targs, pre, owner, true)
You'll see the problem is connected to checking bounds of higher-kinded type, at line 5784, inside checkKindBoundsHK :
if (!sameLength(hkargs, hkparams)) {
if (arg == AnyClass || arg == NothingClass) (Nil, Nil, Nil) // Any and Nothing are kind-overloaded
else {error = true; (List((arg, param)), Nil, Nil) } // shortcut: always set error, whether explainTypesOrNot
}
The test is not passed, it appears that in my debugger:
hkargs$1 = {scala.collection.immutable.Nil$#2541}"List()"
arg$1 = {scala.tools.nsc.symtab.Symbols$ClassSymbol#2689}"class List"
param$1 = {scala.tools.nsc.symtab.Symbols$TypeSymbol#2557}"type B"
paramowner$1 = {scala.tools.nsc.symtab.Symbols$MethodSymbol#2692}"method process"
underHKParams$1 = {scala.collection.immutable.$colon$colon#2688}"List(type R)"
withHKArgs$1 = {scala.collection.immutable.Nil$#2541}"List()"
exceptionResult12 = null
hkparams$1 = {scala.collection.immutable.$colon$colon#2688}"List(type R)"
So it appears like there is one higher kinded param, type R, but there is no provided value for that.
If you actually go back to the to checkKindBounds, you see that after the snippet:
val (arityMismatches, varianceMismatches, stricterBounds) = (
// NOTE: *not* targ.typeSymbol, which normalizes
checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)
)
the arityMismatches contains a tuple List, B. And now you can also see that the error message is wrong:
inferred kinds of the type arguments (MyFoo,MyFoo,List[X]) do not
conform to the expected kinds of the type parameters (type F,type
R,type B). List[X]'s type parameters do not match type B's expected
parameters: class List has one type parameter, but type B has ZERO
In fact if you put a breakpoint at line 5859 on the following call
checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)
you can see that
tparam = {scala.tools.nsc.symtab.Symbols$TypeSymbol#2472}"type B"
targ = {scala.tools.nsc.symtab.Types$UniqueTypeRef#2473}"List[X]"
Conclusion:
For some reason, when dealing with complex higher-kinded types such as yours, Scala compiler inference is limited. I don't know where it does come from, maybe you want to send a bug to the compiler team
I only have a vague understanding of the exact workings of the type inferrer in Scala so consider this ideas not definitive answers.
Type inferring has problems with inferring more then one type at once.
You use an existential type in the definition of FOO, which translates to: there exists a type such, not sure if this is compatible with the specific type given in MyFoo