I wrote the following simple example and expected it to be compiled fine:
abstract class TestObject extends App{
type Type
def method[F[_]](ft: F[Type], t: Test[F]{
type Type = TestObject#Type
}) = t.doSomeAction(ft) //ERROR
}
trait Test[F[_]]{
type Type
def doSomeAction(t: F[Type]) = println(t)
}
ideone demo
But the compiler prints the following error message:
Error:(8, 23) type mismatch;
found : ft.type (with underlying type F[TestObject.this.Type])
required: F[t.Type]
(which expands to) F[TestObject#Type]
Note: TestObject.this.Type <: t.Type, but type F is invariant in type _.
You may wish to define _ as +_ instead. (SLS 4.5)
}) = t.doSomeAction(ft)
I do not really understand it since Test#Type = TestObject#Type.
So the problem is that TestObject#Type captures an existential type which is not what you want. you want to ensure that the Type of specific instances line up. you can do this like this:
https://scalafiddle.io/sf/wpI8iGg/0
or more commonly with the Aux-Pattern
https://scalafiddle.io/sf/wpI8iGg/1
Related
I have a rather simple piece of code that does not compile, as a type can apparently not be inferred. I would like to understand why it does not, as I have plenty of type annotations, and it should work. The code is
object MyApp {
trait A {
type V
val value: V
}
case class ConcreteA(value: Int) extends A { type V=Int }
def convert[T<:A](v: T#V): T = ConcreteA(v.asInstanceOf[Int]).asInstanceOf[T] // brief method with casts for illustration purposes only
def main(args: Array[String]) {
val i: Int = 10
val converted: ConcreteA = convert(i)
}
}
(It compiles if I add an explicit [ConcreteA] on the convert-call)
The error I get is
MyApp.scala:19: error: no type parameters for method convert: (v: T#V)T exist so that it can be applied to arguments (Int)
--- because ---
argument expression's type is not compatible with formal parameter type;
found : Int
required: ?T#V
val converted: ConcreteA = convert(i)
^
MyApp.scala:19: error: type mismatch;
found : Int
required: T#V
val converted: ConcreteA = convert(i)
^
MyApp.scala:19: error: type mismatch;
found : T
required: MyApp.ConcreteA
val converted: ConcreteA = convert(i)
^
three errors found
Can anyone explain this to me?
Edit: I expected Scala to deduce that T is ConcreteA here. I did so because the result of the convert call goes into a val type annotated as such.
The problem, as far as I know, is that type inference works using the Unification algorithm, which simply will try to satisfy the constraints for the given values. It will first try to prove that the input v satisfy the type T#V, but at this moment, it does not have any information about T thus it simply fails before checking if the result type satisfy the condition.
If you explicitly specify that T is ConcreteA then it will be able to continue.
However, you can make it work by postponing the type checking of the arguments after the inference, using Generalized Type Constraints.
def convert[T <: A, V](v: V)(implicit ev: V <:< T#V): T = ???
val converted: ConcreteA = convert(10)
// converted: ConcreteA = ConcreteA(10)
val converted: ConcreteA = convert("hey")
// Compile error: Cannot prove that String <:< T#V.
In this case the value v does not have any restrictions, thus the compiler simple bind V to the type of v which it already knows what it is. Then, it checks the return type T which it knows must be a ConcreteA, which satisfy the constraint that it must be a subtype of A, thus it pass.
After having solved the inference, the compiler attempts to solve the implicits. In this case, it needs to obtain (prove) that V <:< T#V, which in the first case works, but in the second fails.
Here is my code straight & simple:
package scalaproj
import scala.reflect._
case class MyClass() {}
def bar[T](cls : Class[T]) = println(cls)
def foobar[T: ClassTag] = println(classTag[T])
bar(classOf[MyClass])
foobar[MyClass]
Results: class scalaproj.GetFields$MyClass$2
scalaproj.GetFields$MyClass$2
Now I would like to do the following without the famous error: "class type required but T found"
def foo[T] = println(classOf[T])
foo[MyClass]
foo is just a function that takes a Generic Type Parameter and does not need a value parameter. I think this is strange given the two examples that work and all the flexibility build in into the Scala language and its handeling of generics.
Update:
Just to specify further strangeness:
def foo1[T](t : T) = {} // no compile error
def foo2[T](): List[T] = { List[T]() } // no compile error
def foo3[T](): T = { T() } // compile error: "not found: value T"
A good explanation is appreciated.
You can't, as classOf will not work with arbitrary types (and your T is an arbitrary type).
For example:
scala> classOf[Int with String]
<console>:15: error: class type required but Int with String found
classOf[Int with String]
^
You can achieve the same thing with ClassTag#runtimeClass:
def foo[T: ClassTag] = println(classTag[T].runtimeClass)
scala> case class Data[+T](val value:T=null)
defined class Data
scala> val foo=Data[ArrayBuffer[Data[Any]]]()
foo: Data[scala.collection.mutable.ArrayBuffer[Data[Any]]] = Data(null)
scala> foo.value+=Data[String]()
java.lang.NullPointerException
... 33 elided
I would like to have a Data class that is instantiated either as Data[String], Data[ArrayBuffer[Data[Any]]] or Data[Map[String,Data[Any]]]. In the above example I try to instantiate it as Data[ArrayBuffer[Data[Any]]] and add a Data[String] to its arraybuffer. Of course I get a null pointer exception because value is null. But the point of this example that it at least compiles and runs.
Now, in the Data constructor I would like to instantiate value as either a Data[String], an ArrayBuffer[Data[Any]] or Map[String,Data[Any]] depending on the type of the initially null value returned by the getClass method. However for this I need value to be a var, so that I can modify it after examining the type of its null value.
However I get this error:
scala> case class Data[+T](var value:T=null)
<console>:11: error: covariant type T occurs in contravariant position in type T of value value_=
case class Data[+T](var value:T=null)
Make your Data invariant in T. Just remove the +: Data[T] - this should compile.
Better yet, rethink your design to get rid of nulls and mutable variables - they both smell.
Edit: after reading your comments, I understand better what you are trying to do. Consider something like this for example as one of the options.
sealed trait Node
case class ListNode(list: Seq[Node]) extends Node
case class MapNode(map: Map[String, Node]) extends Node
case class LeafNode(data: String) extends Node
Now you can parse your document with something like (this is "pseudocode", adjust it to whatever xml-parsing library you are using):
def parseNode(tag: XMLTag): Node = tag.tagType match {
case LIST =>
val subNodes = tag.getNestedTags.map(parseNode)
ListNode(subNodes)
case MAP =>
val subNodes = tag.getNestedTags.map { tag =>
tag.name -> parseNode(tag)
}
MapNode(subNodes.toMap)
case _ =>
LeafNode(tag.text)
}
http://like-a-boss.net/2012/09/17/variance-in-scala.html#variance_and_type_safety
Variance and type safety
When defining a generic class with a var field we can get compile time errors:
scala> class Invariant[T](var t: T)
defined class Invariant
scala> class Covariant[+T](var t: T)
<console>:7: error: covariant type T occurs in contravariant position in type T of value t_=
class Covariant[+T](var t: T)
^
scala> class Contravariant[-T](var t: T)
<console>:7: error: contravariant type T occurs in covariant position in type => T of method t
class Contravariant[-T](var t: T)
Let’s break it down a little. Why doesn’t the compiler allow getters in the Covariant class?
scala> abstract trait Covariant[+T] {
| def take(t: T): Unit
| }
<console>:8: error: covariant type T occurs in contravariant position in type T of value t
def take(t: T): Unit
^
scala> abstract trait Contravariant[-T] {
| def take(t: T): Unit
| }
defined trait Contravariant
Why? Let’s think about usages of covariance let’s say that we have a class:
class Printer[+T] {
| def print(t: T): Unit = ???
| }
<console>:8: error: covariant type T occurs in contravariant position in type T of value t
def print(t: T): Unit = ???
If the print method can print Dogs does it make sense (in general) that it should also print Animals? Maybe sometimes but in the general sense if we want to generalize the Printer class we should use contravariance. The compiler is smart enough to check this type of usage for us.
Let’s think about the second use case: returning a generic parameter:
scala> class Create[-T] {
| def create: T = ???
| }
<console>:8: error: contravariant type T occurs in covariant position in type => T of method create
def create: T = ???
And again - does it make sense that Create should generalize by contravariance? If Create returns instances of the Animal class should we be able to use it in every place that expects Create[Dog]? The scala compiler is smart enough that it explodes in our face if we try it.
I can make it work this way:
package data
case class Data[+T](val value:T)
{
println(value.getClass)
}
This way I have to explicitly initialize value from the constructor. Nothing wrong with this, just I find it a bit too verbose.
import data.Data
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Map
object Run extends App
{
val a=Data[ArrayBuffer[Data[Any]]](ArrayBuffer[Data[Any]]())
a.value+=Data[String]("bar")
println(a.value)
val m=Data[Map[String,Data[Any]]](Map[String,Data[Any]]())
m.value+=("foo"->Data[String]("bar"))
println(m.value)
}
This prints:
class scala.collection.mutable.ArrayBuffer
class java.lang.String
ArrayBuffer(Data(bar))
class scala.collection.mutable.HashMap
class java.lang.String
Map(foo -> Data(bar))
The program only compiles with Data having +T type parameter, otherwise I get the error:
type mismatch;
[error] found : data.Data[String]
[error] required: data.Data[Any]
[error] Note: String <: Any, but class Data is invariant in type T.
[error] You may wish to define T as +T instead. (SLS 4.5)
[error] a.value+=Data[String]("bar")
When trying to use higher kinded existentials in Scala I run into the following problem:
trait A[H[_]]
trait Test {
val l: A[List]
// [error] type mismatch;
// [error] found : A[List]
// [error] required: A[_[_] <: Any]
// [error] Note: List <: Any, but trait A is invariant in type H.
// [error] You may wish to define H as +H instead. (SLS 4.5)
val x: A[B] forSome { type B[_] } = l
}
Adding a covariant annotation to H as the compiler suggests works. Is there a way to work around this if I don't want H to be covariant?
A slight variation of the example gives a more helpful error message:
scala> l: (A[B] forSome { type B[_] })
<console>:10: error: type mismatch;
found : A[List]
required: A[_[_] <: Any]
Note: List <: Any, but trait A is invariant in type H.
You may wish to define H as +H instead. (SLS 4.5)
l: (A[B] forSome { type B[_] })
^
<console>:10: error: can't existentially abstract over parameterized type B
l: (A[B] forSome { type B[_] })
^
Looking for this error brings us to a TODO in the compiler.
Since existential types are going to disappear, per Odersky's email, I don't think this limitation will be fixed. However, Martin Odersky's email also reminds us that existential types are equivalent to abstract types. Hence, the above example can be encoded as follows:
scala> trait A { type H[_] }
defined trait A
scala> val l: A {type H[X] = List[X]} = null
l: A{type H[X] = List[X]} = null
scala> l: A
res0: A = null
Type application is syntactically ugly, but turning a value into an existential becomes trivial (also to implement in a compiler, which is part of Odersky's point).
What's useful to encode existentials is that type members don't have to be instantiated ever. So, to encode A[_] we can write:
scala> class A { type T }
defined class A
scala> new A
res1: A = A#3a7049a6
What's confusing here is that this does not work for objects:
scala> object B { type T }
<console>:8: error: only classes can have declared but undefined members
object B { type T }
^
But I recently got that accepted as a bug — see here for a pull request clarifying the spec (approved by Adriaan Moors), and here for my bug report and one-line fix to the compiler (still waiting for review).
Let's say I have this trait
trait Ctx[C, V[_]]
I am unable to construct any method signature that takes a Ctx of which the second type parameter is unspecified (wildcard). E.g. this:
def test(c: Ctx[_, _]) = ()
doesn't compile ("error: _$2 takes no type parameters, expected: one"). Neither can I do
def test(c: Ctx[_, _[_]]) = ()
("error: _$2 does not take type parameters"). What am I missing?
I'm able to define this one:
def test[V[X]](c:Ctx[_,V]) {}
And it seems to work ok with type inference:
scala> trait Ctx[ C, V[ _ ]]
defined trait Ctx
scala> def test[V[X]](c:Ctx[_,V]) {}
test: [V[X]](c: Ctx[_, V])Unit
scala> class P extends Ctx[Int, List]
defined class P
scala> new P
res0: P = P#1f49969
scala> test(res0)
Edit: I suspect it won't be practical to replace Ctx to use an abstract type, but this is what I was able to do:
trait Ctx[C] { type V[X] }
class CtxOption[C] extends Ctx[C] { type V[X] = Option[X] }
class CtxList[C] extends Ctx[C] { type V[X] = List[X] }
def test(ctx:Ctx[_]) { println(ctx) }
val ctxOptInt = new CtxOption[Int]
val ctxListStr = new CtxList[String]
test(ctxOptInt)
test(ctxListStr)
val list = collection.mutable.ListBuffer[Ctx[_]]()
list += ctxOptInt
list += ctxListStr
list
Using an abstract type for V spares you the complicated (or impossible) task of figuring the type parameter syntax for a wildcard type constructor. Additionally as demonstrated in the ListBuffer example you can then handle objects where the V is a different type constructor (Option and List in my example). The first solution I provided would not allow you to do that.
Edit 2: How about?
trait AbstractCtx[C] { type W[X] }
trait Ctx[C,V[_]] extends AbstractCtx[C] { type W[X] = V[X] }
def test(ctx:AbstractCtx[_]) { println(ctx) }
You need to pass a type constructor for the second argument of Ctx. Scala is not able to infer the correct kind if you just pass _. Neither is it possible to define a type constructor with wildcards (i.e. _[_]] on the fly. Note that in your first example _$2 in the error message refers to the type passed as second argument to Ctx as a whole. In the second example however _$2 refers to the the first wildcard type in _[_]. See the location indicator in the error messages:
<console>:6: error: _$2 does not take type parameters
def test( c: Ctx[ _, _[ _ ]]) {}
^
The following works since here V is a type constructor of the right kind expected by Ctx.
def test[V[_]]( c: Ctx[ _, V]) {}