This doesn't compile:
class MyClass[+A] {
def myMethod(a: A): A = a
}
//error: covariant type A occurs in contravariant position in type A of value a
Alright, fair enough. But this does compile:
class MyClass[+A]
implicit class MyImplicitClass[A](mc: MyClass[A]) {
def myMethod(a: A): A = a
}
Which lets us circumvent whatever problems the variance checks are giving us:
class MyClass[+A] {
def myMethod[B >: A](b: B): B = b //B >: A => B
}
implicit class MyImplicitClass[A](mc: MyClass[A]) {
def myExtensionMethod(a: A): A = mc.myMethod(a) //A => A!!
}
val foo = new MyClass[String]
//foo: MyClass[String] = MyClass#4c273e6c
foo.myExtensionMethod("Welp.")
//res0: String = Welp.
foo.myExtensionMethod(new Object())
//error: type mismatch
This feels like cheating. Should it be avoided? Or is there some legitimate reason why the compiler lets it slide?
Update:
Consider this for example:
class CovariantSet[+A] {
private def contains_[B >: A](b: B): Boolean = ???
}
object CovariantSet {
implicit class ImpCovSet[A](cs: CovariantSet[A]) {
def contains(a: A): Boolean = cs.contains_(a)
}
}
It certainly appears we've managed to achieve the impossible: a covariant "set" that still satisfies A => Boolean. But if this is impossible, shouldn't the compiler disallow it?
I don't think it's cheating any more than the version after desugaring is:
val foo: MyClass[String] = ...
new MyImplicitClass(foo).myExtensionMethod("Welp.") // compiles
new MyImplicitClass(foo).myExtensionMethod(new Object()) // doesn't
The reason is that the type parameter on MyImplicitClass constructor gets inferred before myExtensionMethod is considered.
Initially I wanted to say it doesn't let you "circumvent whatever problems the variance checks are giving us", because the extension method needs to be expressed in terms of variance-legal methods, but this is wrong: it can be defined in the companion object and use private state.
The only problem I see is that it might be confusing for people modifying the code (not even reading it, since those won't see non-compiling code). I wouldn't expect it to be a big problem, but without trying in practice it's hard to be sure.
You did not achieve the impossible. You just chose a trade-off that is different from that in the standard library.
What you lost
The signature
def contains[B >: A](b: B): Boolean
forces you to implement your covariant Set in a way that works for Any, because B is completely unconstrained. That means:
No BitSets for Ints only
No Orderings
No custom hashing functions.
This signature forces you to implement essentially a Set[Any].
What you gained
An easily circumventable facade:
val x: CovariantSet[Int] = ???
(x: CovariantSet[Any]).contains("stuff it cannot possibly contain")
compiles just fine. It means that your set x, which has been constructed as a set of integers, and can therefore contain only integers, will be forced to invoke the method contains at runtime to determine whether it contains a String or not, despite the fact that it cannot possibly contain any Strings. So again, the type system doesn't help you in any way to eliminate such nonsensical queries which will always yield a false.
Related
I came across this article on medium: https://medium.com/#odomontois/tagless-unions-in-scala-2-12-55ab0100c2ff. There is a piece of code that I have a hard time understanding. The full source code for the article can be found here: https://github.com/Odomontois/zio-tagless-err.
The code is this:
trait Capture[-F[_]] {
def continue[A](k: F[A]): A
}
object Capture {
type Constructors[F[_]] = F[Capture[F]]
type Arbitrary
def apply[F[_]] = new Apply[F]
class Apply[F[_]] {
def apply(f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
def continue[A](k: F[A]): A = f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
}
}
}
Here are my questions:
how does the scala compiler solve/handle the Arbitrary type given that the type is declared in an object? It seems to depend on the apply method parameter type but then how does that square to the fact that Capture is an object and you can have multiple apply calls with different types? I came across this post What is the meaning of a type declaration without definition in an object? but it is still not clear to me.
according to the article the code above uses a trick from another library https://github.com/alexknvl. Could you please explain what is the idea behind this pattern? What is it for? I understand that the author used it in order to capture the multiple types of errors that can occur during the login process.
Thanks!
Update:
First question:
Based on the spec when the upper bound is missing it is assumed to be Any. So, Arbitrary is treated as Any, however, it doesn't seem interchangeable with Any.
This compiles:
object Test {
type Arbitrary
def test(x: Any): Arbitrary = x.asInstanceOf[Arbitrary]
}
however, this doesn't:
object Test {
type Arbitrary
def test(x: Any): Arbitrary = x
}
Error:(58, 35) type mismatch;
found : x.type (with underlying type Any)
required: Test.Arbitrary
def test(x: Any): Arbitrary = x
See also this scala puzzler.
This is a little obscure, though legal usage of type aliases. In specification you can read type alias might be used to refer to some abstract type and be used as a type constraint, suggesting compiler what should be allowed.
type X >: L <: U would mean that X, whatever it is, should be bound between L and G - and actually any value that we know fulfill that definition can be used there,
type X = Y is very precise constraint - compiler know that each time we have Y we can call it Y and vice versa
but type X is also legal. We use it usually to declare it in trait or something, but then we put more constraints on it in extending class.
trait TestA { type X }
trait TestB extends TestA { type X = String }
however, we don't have to specify it to a concrete type.
So the code from the question
def apply(f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
def continue[A](k: F[A]): A = f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
}
can be read as: we have Arbitrary type we know nothing of, but we know that if we put F[Arbitrary] into a function, we get Arbitrary.
Thing is, compiler will not let you pass any value as Arbitrary because it cannot prove that your value is of this type. If it could prove that Arbitrary=A you could just write:
def apply(f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
def continue[A](k: F[A]): A = f(k)
}
However, it cannot, which is why you are forced to use .asInstanceOf. That is why type X is not equal to saying type X = Any.
There is a reason, why we aren't simply using generics though. How would you pass in a polymorphic function inside? One that does F[A] => A for any A? One way would be to use natural transformation (or ~> or FunctionK) from F[_] to Id[_]. But how messy it would be to use it!
// no capture pattern or other utilities
new Capture[F] {
def continue[A](fa: F[A]): A = ...
}
// using FunctionK
object Capture {
def apply[F[_]](fk: FunctionK[F, Id]): Caputure[F] = new Capture[F] {
def continue[A](fa: F[A]): A = fk(fa)
}
}
Capture[F](new FunctionK[F, Id] {
def apply[A](fa: F[A]): A = ...
})
Not pleasant. Problem is, you cannot pass something like polymorphic function (here [A]: F[A] => A). You can only pass instance with polymorphic method (that is FunctionK works).
So we are hacking this by passing a monomorphoc function with A fixed to type that you cannot instantiate (Arbitrary) because for no type compiler can prove that it matches Arbitrary.
Capture[F](f: F[Arbitrary] => Arbitrary): Capture[F]
Then you are forcing the compiler into thinking that it is of type F[A] => A when you learn A.
f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
The other part of the pattern is partial application of type parameters of sort. If you did things in one go:
object Capture {
type Constructors[F[_]] = F[Capture[F]]
type Arbitrary
def apply[F[_]](f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
def continue[A](k: F[A]): A = f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
}
}
you would have some issues with e.g. passing Capture.apply as a normal function. You would have to do things like otherFunction(Capture[F](_)). By creating Apply "factory" we can split type parameter application and passing the F[Arbitrary] => Arbitrary function.
Long story short, it is all about letting you just write:
takeAsParameter(Capture[F]) and
Capture[F] { fa => /* a */ }
Suppose you have a trait like this:
trait Foo[A]{
def foo: A
}
I want to create a function like this:
def getFoo[A <: Foo[_]](a: A) = a.foo
The Scala Compiler deduces Any for the return type of this function.
How can I reference the anonymous parameter _ in the signature (or body) of getFoo?
In other words, how can I un-anonymize the parameter?
I want to be able to use the function like
object ConcreteFoo extends Foo[String] {
override def foo: String = "hello"
}
val x : String = getFoo(ConcreteFoo)
which fails compilation for obvious reasons, because getFoo is implicitly declared as Any.
If this is not possible with Scala (2.12 for that matter), I'd be interested in the rational or the technical reason for this limitation.
I am sure there are articles and existing questions about this, but I appear to be lacking the correct search terms.
Update: The existing answer accurately answers my question as stated, but I suppose I wasn't accurate enough regarding my actual usecase. Sorry for the confusion. I want to be able to write
def getFoo[A <: Foo[_]] = (a: A) => a.foo
val f = getFoo[ConcreteFoo.type]
//In some other, unrelated place
val x = f(ConcreteFoo)
Because I don't have a parameter of type A, the compiler can't deduce the parameters R and A if I do
def getFoo[R, A <: Foo[R]]: (A => R) = (a: A) => a.foo
like suggested. I would like to avoid manually having to supply the type parameter R (String in this case), because it feels redundant.
To answer literally your exact question:
def getFoo[R, A <: Foo[R]](a: A): R = a.foo
But since you don't make any use of the type A, you can actually omit it and the <: Foo[..] bound completely, retaining only the return type:
def getFoo[R](a: Foo[R]): R = a.foo
Update (the question has been changed quite significantly)
You could smuggle in an additional apply invocation that infers the return type from a separate implicit return type witness:
trait Foo[A] { def foo: A }
/** This is the thing that remembers the actual return type of `foo`
* for a given `A <: Foo[R]`.
*/
trait RetWitness[A, R] extends (A => R)
/** This is just syntactic sugar to hide an additional `apply[R]()`
* invocation that infers the actual return type `R`, so you don't
* have to type it in manually.
*/
class RetWitnessConstructor[A] {
def apply[R]()(implicit w: RetWitness[A, R]): A => R = w
}
def getFoo[A <: Foo[_]] = new RetWitnessConstructor[A]
Now it looks almost like what you wanted, but you have to provide the implicit, and you have to call getFoo[ConcreteFoo.type]() with additional pair of round parens:
object ConcreteFoo extends Foo[String] {
override def foo: String = "hey"
}
implicit val cfrw = new RetWitness[ConcreteFoo.type, String] {
def apply(c: ConcreteFoo.type): String = c.foo
}
val f = getFoo[ConcreteFoo.type]()
val x: String = f(ConcreteFoo)
I'm not sure whether it's really worth it, it's not necessarily the most straightforward thing to do. Type-level computations with implicits, hidden behind somewhat subtle syntactic sugar: that might be too much magic hidden behind those two parens (). Unless you expect that the return type of foo will change very often, it might be easier to just add a second generic argument to getFoo, and write out the return type explicitly.
So, let's say I have a class with a contravariant type parameter:
trait Storage[-T] {
def list(path: String): Seq[String]
def getObject[R <: T](path: String): Future[R]
}
The idea of the type parameter is to constrain the implementation to the upper boundary of types that it can return. So, Storage[Any] can read anything, while Storage[avro.SpecificRecord] can read avro records, but not other classes:
def storage: Storage[avro.SpecificRecord]
storage.getObject[MyAvroDoc]("foo") // works
storage.getObject[String]("bar") // fails
Now, I have a utility class that can be used to iterate through objects in a given location:
class StorageIterator[+T](
val storage: Storage[_ >: T],
location: String
)(filter: String => Boolean) extends AbstractIterator[Future[T]] {
val it = storage.list(location).filter(filter)
def hasNext = it.hasNext
def next = storage.getObject[T](it.next)
}
This works, but sometimes I need to access the underlying storage from the iterator downstream, to read another type of object from an aux location:
def storage: Storage[avro.SpecificRecord]
val iter = new StorageIterator[MyAvroDoc]("foo")
iter.storage.getObject[AuxAvroDoc](aux)
This does not work, of course, because storage type parameter is a wildcard, and there is no proof that it can be used to read AuxAvroDoc
I try to fix it like this:
class StorageIterator2[P, +T <: P](storage: Storage[P])
extends StorageIterator[T](storage)
This works, but now I have to specify two type params when creating it, and that sucks :(
I tried to work around it by adding a method to the Storage itself:
trait Storage[-T] {
...
def iterate[R <: T](path: String) =
new StorageIterator2[T, R](this, path)
}
But this doesn't compile because it puts T into an invariant position :(
And if I make P contravariant, then StorageIterator2[-P, +T <: P] fails, because it thinks that P occurs in covariant position in type P of value T.
This last error I don't understand. Why exactly cannot P be contravariant here? If this position is really covariant (why is it?) then why does it allow me to specify an invariant parameter there?
Finally, does anyone have an idea how I can work around this?
Basically, the idea is to be able to
Do storage.iterate[MyAvroDoc] without having to give it the upper boundary again, and
Do iterator.storage.getObject[AnotherAvroDoc] without having to cast the storage to prove that it can read this type of object.
Any ideas are appreciated.
StorageIterator2[-P, +T <: P] fails because it is nonsensical. If you have a StorageIterator2[Foo, Bar], and Bar <: Foo, then because it is contravariant in the first parameter, it is also a StorageIterator[Nothing, Bar], but Nothing has no subtypes, so it is logically impossible that Bar <: Nothing, yet this is what must be true to have a StorageIterator2. Therefore, StorageIterator2 cannot exist.
The root problem is that Storage should not be contravariant. Think of what a Storage[T] is, in terms of the contract it gives to its users. A Storage[T] is an object that you give paths to, and will output Ts. It makes perfect sense for Storage to be covariant: something that knows how to output Strings, for example, is also outputting Anys, so it makes logical sense that Storage[String] <: Storage[Any]. You say that it should be the other way around, that a Storage[T] should know how to output any subtype of T, but how would that work? What if someone adds a subtype to T after the fact? T can even be final and still have this problem, because of singleton types. That is unnecessarily complicated, and is reflected in your problem. That is, Storage should be
trait Storage[+T] {
def list(path: String]: Seq[String]
def get(path: String): T
}
This does not open you up to the example mistake you gave in your question:
val x: Storage[avro.SpecificRecord] = ???
x.get(???): avro.SpecificRecord // ok
x.get(???): String // nope
(x: Storage[String]).get(???) // nope
Now, your issue is that you can't do something like storage.getObject[T] and have the cast be implicit. You can instead a match:
storage.getObject(path) match {
case value: CorrectType => ...
case _ => // Oops, something else. Error?
}
a plain asInstanceOf (undesirable), or you can add a helper method to Storage, like the one you had before:
def getCast[U <: T](path: String)(implicit tag: ClassTag[U]): Option[U] = tag.unapply(get(path))
I was writing a wrapper class, that passes on most calls identically to the root object and I accidentally left the full definition (with parameter name x, etc) see below. To my surprise, it compiled. So what is going on here? Is this similar to assigning to root.p_ ? I find it strange that I can leave the name "x" in the assigment. Also, what would be the best (fastest) way to pass on wrapped calls - or maybe it makes no difference?
trait A {
def p(x:Int) = println("A"+123)
}
case class B(root:A) {
def p(x: Int): Unit = root.p(x:Int) // WHAT HAPPENED HERE?
}
object Test extends App {
val temp = new A{}
val b = B(temp)
b.p(123)
}
What happens is type ascription, and here, it is not much.
The code works just as if you had written
def p(x: Int): Unit = root.p(x)
as you intended. When you write x: Int in the call (not in the declaration, where it has a completely different meaning) or more generally expr: Type, it has the same value as expr, but it tells the compiler to check that the expr is of the given type (this is a check made a compile type, sort of an upcast, not at all a runtime check such as asInstanceOf[...]) and to treat it has having that type. Here, x is indeed an Int and it is already treated as an Int by the compiler, so the ascription changes nothing.
Besides documenting a non obvious type somewhere in the code, type ascription may be used to select between overloaded method:
def f(a: Any) ...
def f(i: Int) ...
f(3) // calls f(i: Int)
f(3: Any) // calls f(a: Any)
Note that in the second call, with the ascription, the compiler knows that 3 is of type Any, less precise than Int, but still true. That would be an error otherwise, this is not a cast. But the ascription makes it call the other version of f.
You can have a look at that answer for more details: https://stackoverflow.com/a/2087356/754787
Are you delegating the implementation of B.p to A.p ?
I don't see any unusual except for root.p(x:Int), you can save typing by root.p(x).
trait is a way of code mixin, I think the easiest way is:
trait A {
def p(x: Int) = println("A" + x)
}
case class B extends AnyRef with A
val b = B()
b.p(123)
Is it possible to write a method in Scala which returns an object of a type-parameterized class with different type paramter ? Something like this:
class A[T]
def f(switch: Boolean): A = if(switch) new A[Int] else new A[String]
Please note: The Code above is fictional to show the type of problem; The code above does not make semantically sense.
The code above will not compile because return type A is not parameterized.
You can, and you can even do it with type-safety with the aid of implicit arguments that encapsulate the pairings:
class TypeMapping[+A,B] {
def newListB = List.empty[B]
}
trait Logical
object True extends Logical
object False extends Logical
implicit val mapFalseToInt = new TypeMapping[False.type,Int]
implicit val mapTrueToString = new TypeMapping[True.type,String]
def f[A <: Logical,B](switch: A)(implicit tmap: TypeMapping[A,B]) = tmap.newListB
scala> f(True)
res2: List[String] = List()
scala> f(False)
res3: List[Int] = List()
You do have to explicitly map from boolean values to the custom True and False values.
(I have chosen List as the target class just as an example; you could pick anything or even make it generic with a little more work.)
(Edit: as oxbow_lakes points out, if you need all possible return values to be represented on the same code path, then this alone won't do it, because the superclass of List[Int] and List[String] is List[Any], which isn't much help. In that case, you should use an Either. My solution is for a single function that will be used only in the True or False contexts, and can maintain the type information there.)
One way of expressing this would be by using Either;
def f(switch: Boolean) = if (switch) Left(new A[Int]) else Right(newA[String])
This of course returns an Either[A[Int], A[String]]. You certainly cannot (at the moment) declare a method which returns some parameterized type P, with some subset of type parameters (i.e. only Int or String).
The language ceylon has union types and I understand the intention is to add these to scala in the near future, in which case, you could define a method:
def f(switch: Boolean): A[Int|String] = ...
Well, you could do something like that.
scala> class A {
| type T
| }
defined class A
scala> def f(b: Boolean): A = if(b) new A { type T = Int } else new A { type T = String }
f: (b: Boolean)A
But this is pointless. Types are a compile time information, and that information is getting lost here.
How about an absolutely minimal change to the "fictional code"? If we just add [_] after the "fictional" return type, the code will compile:
class A[T]
def f(switch: Boolean):A[_] = if(switch) new A[Int] else new A[String]
It is worth noting that A[_] is not the same as A[Any]. A[T] does not need to be defined covariant for the code to compile.
Unfortunately, information about the type gets lost.