I've been learning about scala's use of covariance and contravariance parameterized typing; I'm a bit perplexed by the following example code:
class Thing
class Creature extends Thing
class Human extends Creature
class Dog extends Creature
class Home[T >: Creature] {
private var inside = Set[T]()
def enter(entering: T) = inside += entering
def whoIsInside = inside
}
val house = new Home[Thing]
val cage = new Home[Creature]
house enter new Human
cage enter new Dog
As I understand it, the parameterized class Home uses contravariance with a lower bound of Creature, so
val flat = new Home[Human]
causes a compiler error, which is what I expected. My predicament is, I've created a new 'house' but I can put a 'Human' in it! While this also makes sense because a 'Human' is a 'Thing' I was naively expecting it to fail! Putting aside the mechanics, how is covariance, contravariance useful?
In your example, you can put subtypes of T into the collection of T, because U <: T is also a T. That is not covariance or contravariance.
Covariance would be when your House[U] is a subtype of a House[T] for U <: T. So if for example you asked for a House[Creature] you could offer a House[Human] if T was covariant. We use the + on a type parameter for covariance.
Eg,
class Home[+T]
val cage: Home[Creature] = new Home[Human]
The most useful example of this is when you use Nil for a List, because Nil is a List[Nothing] and List is covariant. So a List[Nothing] can substitute for any type of List at all.
Contravariance is the opposite. That a House[U] is a supertype of House[T] if U <: T. We use the - on a type parameter for contravariance.
Eg,
class Home[-T]
val cage: Home[Human] = new Home[Creature]
Before answering the question, I need to note two things:
a) you are not using covariance / contravariance in your example, you merely define a lower bound
b) in the title, you imply that this is a Scala concept. Covariance and contravariance are general concepts in object orientation, and have existed long before Scala.
So regarding your original question, how is it useful? It allows you to specify inheritance for parameterized types. For example, if you defined you type parameter as covariant, maybe with Creature as an upper bound, you could express that a dog's home can stand in for a creature's home:
scala> class Home[+T <: Creature]
defined class Home
scala> var home = new Home[Creature]
home: Home[Creature] = Home#46a32efb
scala> home = new Home[Dog]
home: Home[Creature] = Home#1b955e70
So Home[Dog] is a subtype of of Home[Creature] - covariance allows you to express this.
Also note that in your example just making the type parameter covariant would not compile, as you allow entering. The method parameter can not be covariant, as that would break the substitutability. The Scala compiler will detect this for you.
Related
Here is a simple example:
trait Base {
type Out
def v: Out
}
object Base {
type Aux[T] = Base { type Out = T }
class ForH() extends Base {
type Out = HNil
override def v: Out = HNil
}
object ForH extends ForH
}
class TypeClass[B]
trait TypeClassLevel1 {
def summon[B](b: B)(implicit ev: TypeClass[B]): TypeClass[B] = ev
}
object TypeClass extends TypeClassLevel1 {
implicit def t1: TypeClass[Base.Aux[HNil]] = new TypeClass[Base.Aux[HNil]]
implicit def t2: TypeClass[Int] = new TypeClass[Int]
}
it("No Aux") {
val v = 2
TypeClass.summon(v) // works
}
it("Aux") {
val v = new Base.ForH()
TypeClass.summon(v) // oops
TypeClass.summon(Base.ForH) // oops
val v2 = new Base.ForH(): Base.Aux[HNil]
TypeClass.summon(v2) // works!
}
The object Base/ForH clearly has a stable path, this eliminate the possibility of the compiler not being able to resolve type ForH.Out.
What bothers me is not how incapable the compiler is to figure out the fact that ForH <:< Aux[HNil], but how easy it is to patch it up, just by a simple type upcast (last 2 lines). IMHO both features (type lambda & type classes) are important aspect of functional programming, why they can't work together at the same time?
If you are familiar with the compiler design I'll have an extra question: what does it take to improve the type class search algorithm to make it happen? Thanks a lot for your opinion.
UPDATE 1: a specific fix has been proposed but I have another problem trying to generalise it, please see In scala, how to make type class working for Aux pattern? - Part 2 for detail
So the compiler is able to infer ForH <:< Aux[HNil], but (tbh I don't know exactly why) when resolving an implicit when the return type uses a type lambda, it gets confused if you don't use a type bound.
Anyway that's probably not a great explanation, but at least I can make your code compile. Just change t1 to:
implicit def t1[T <: Base.Aux[HNil] ]: TypeClass[T] = new TypeClass[T]
this works for me in scastie using Scala 2.13.4.
What bothers me is not how incapable the compiler is to figure out the fact that ForH <:< Aux[HNil]
Surely the compiler does see that Base.ForH <:< Base.Aux[HNil]. You can check that
implicitly[Base.ForH <:< Base.Aux[HNil]]
compiles.
IMHO both features (type lambda & type classes) are important aspect of functional programming, why they can't work together at the same time?
Why are you talking about type lambdas? I can't see type lambdas in your question.
By the way, type lambdas is not a part of Scala 2 and ({ type λ[X] = ...F[X]... })#λ for a type lambda is more or less a hack. Actual type lambdas are added to Scala 3.
val v = new Base.ForH() has type Base.ForH (not Base.Aux[HNil] without upcasting via type ascription val v = new Base.ForH(): Base.Aux[HNil] or manual type specification val v: Base.Aux[HNil] = new Base.ForH()). TypeClass.summon(v) shouldn't compile since there is no implicit TypeClass[Base.ForH]. What implicit would you consider as a candidate? TypeClass.t1? But it's not a candidate, you can check that explicitly resolved
TypeClass.summon(v)(TypeClass.t1)
can't compile.
what does it take to improve the type class search algorithm to make it happen?
Implicit search algorithm shouldn't be improved in this specific place. It works properly, as intended.
You can make the type class contravariant
class TypeClass[-B]
Then TypeClass.t1 will be a candidate for TypeClass[Base.ForH] and TypeClass.summon(v) will compile.
In scala 2.13, how to use implicitly[value singleton type]?
Let's say I have:
trait X {
val x: String
}
Using mix-in, I can define a trait such as
trait XPrinter {
self: X =>
def printX: String = "X is: " + x
}
such that a value/object implementing XPrinter implements x and give its methods such as printX access to the values specified in X such as x.
So far, so good.
I want to know if there is a way of having a trait in the form of:
trait XDependent[T <: X] {
def printX: String = ???
}
So that XDependent instances have access to the value of T.x, with x assumed to be a "static value" glued with the type definition.
Now I understand why T.x can't be accessed in XDependent since a type subtyping X doesn't even have to implement the value of x and T.x might be abstract.
I understand that while Scala offers path-dependent types so that an abstract type defined in X can be used in XDependent, as shown here:
trait X {
type Y //which can be constrained as desired.
}
trait XDependent[T <: X]{
def foo(v:T#Y)
def bar: T#Y
}
it doesn't offer the same thing with values as there is a clear separation between types and values in Scala.
Now I have come across the ideas of value-dependent types and literal-based types. I want to know if the idea of "static value for types", as illustrated above, has much overlap with the these concepts and what the connections are.
I'd also like to know about the different approaches taken in different languages, to blur the separation between types and values, how compatible they are with Scala's type system, and what the complications are in terms of integrating "static values" with the type-system. ie, (Can they be)/ (what happens if they are) overriden by a subtype, etc.
If you can relax the requirement that XDependent has to be a trait, and make it an abstract class instead, then it seems as if a typeclass which provides a single null-ary method x is exactly what you want:
Here is your base trait X (without X.x or anything, that wouldn't be "static"):
trait X
Now you can define a typeclass HasStaticX[T] that guarantees that for a type T we can give some string x:
trait HasStaticX[T] {
def x: String
}
Then you can use it like this:
abstract class XDependent[T <: X : HasStaticX] {
def printX: String = implicitly[HasStaticX[T]].x
}
What HasStaticX does is essentially building a compile-time partial function that can take type T and produce a string-value x associated with T. So, in a way, it's something like a function that takes types and returns values. If this is what you want, then nothing has to be done to for "integrating static values", it just works in the current non-experimental mainstream versions of Scala.
The "value-dependent types" would be exactly the other way round: those would be essentially "functions" that assign types to values.
I have an issue working with existentials in Scala. My problem started when creating a mini workflow engine. I started on the idea that it was a directed graph, implemented the model for the latter first and then modeled the Workflow like this:
case class Workflow private(states: List[StateDef], transitions: List[_, _], override val edges: Map[String, List[StateDef]]) extends Digraph[String, StateDef, Transition[_, _]](states, edges) { ... }
In this case class, the first two fields are a list of states which behave as node, transitions which behave as edges.
The Transition parameter types are for the input and output parameters, as this should behave as an executable piece in the workflow, like a function of some sort:
case class Transition[-P, +R](tailState: StateDef, headState: StateDef, action: Action[P, R], condition: Option[Condition[P]] = None) extends Edge[String, StateDef](tailState, headState) {
def execute(param: P): Try[State[R]] = ...
}
I realized soon enough that dealing with a list of transitions in the Workflow object was giving me troubles with its type parameters. I tried to use parameters with [[Any]] and [[Nothing]], but I couldn't make it work (gist [1]).
If I'd do Java, I'd use a wildcard ? and use its 'less type safe and more dynamic' property and Java would have to believe me. But Scala is stricter and with variance and covariance of the Transition parameter types, it's hard to define wildcards and handle these properly. For example, using forSome notation and having a method in Workflow, I would get this error (gist [2]):
Error:(55, 24) type mismatch;
found : List[A$A27.this.Transition[A$A27.this.CreateImage,A$A27.this.Image]]
required: List[A$A27.this.Transition[P forSome { type P },R forSome { type R }]]
lazy val w = Workflow(transitions)
^
Hence then I created an existential type based on a trait (gist [3]), as explained in this article.
trait Transitions {
type Param
type Return
val transition: Transition[Param, Return]
val evidenceParam: StateValue[Param]
val evidenceReturn: StateValue[Return]
}
So now I could plug this existential in my Workflow class like this:
case class Workflow private(states: List[StateDef], transitions: List[Transitions], override val edges: Map[String, List[StateDef]])
extends Digraph[String, StateDef, Transitions](states, edges)
Working in a small file proved to be working (gist [3]). But when I moved on to the real code, my Digraph parent class does not like this Transitions existential. The former needs an Edge[ID, V] type, which Transition complies with but not the Transitions existential of course.
How in Scala does one resolve this situation? It seems troublesome to work with parameter types to get generics in Scala. Is there an easier solution that I haven't tried? Or a magic trick to specify the correct compatible parameter type between Digraph which need an Edge[ID, V] type and not an existential type that basically erase type traces?
I am sorry as this is convoluted, I will try my best to update the question if necessary.
Here are the Gist references for some of my trials and errors:
https://gist.github.com/jimleroyer/943efd00c764880b8119786d9dd6c3a2
https://gist.github.com/jimleroyer/1ce238b3934882ddc02a09485f52f407
https://gist.github.com/jimleroyer/17227b7e334d020a21deb36086b9b978
EDIT-1
Based on #HTNW answer, I've modified the scope of the existentials using forSome and updated the solution: https://gist.github.com/jimleroyer/2cb4ccbec13620585d21d53b4431ce22
I still have an issue though to properly bind the generics with the matchTransition & getTransition methods and without an explicit cast using asInstanceOf. I'll open another question specific to that one issue.
You scoped your existential quantifiers wrong.
R forSome { type R }
is equal to Any, because every single type is a type, so every single type is a subtype of that existential type, and that is the distinguishing feature of Any. Therefore
Transition[P forSome { type P }, R forSome { type R }]
is really
Transition[Any, Any]
and the Transitions end up needing to take Anys as parameter, and you lose all information about the type of the return. Make it
List[Transition[P, R] forSome { type P; type R }] // if every transition can have different types
List[Transition[P, R]] forSome { type P; type R } // if all the transitions need similar types
// The first can also be sugared to
List[Transition[_, _]]
// _ scopes so the forSome is placed outside the nearest enclosing grouping
Also, I don't get where you got the idea that Java's ? is "less safe". Code using it has a higher chance of being unsafe, sure, because ? is limited, but on its own it is perfectly sound (modulo null).
I just learned Scala. Now I am confused about Contravariance and Covariance.
From this page, I learned something below:
Covariance
Perhaps the most obvious feature of subtyping is the ability to replace a value of a wider type with a value of a narrower type in an expression. For example, suppose I have some types Real, Integer <: Real, and some unrelated type Boolean. I can define a function is_positive :: Real -> Boolean which operates on Real values, but I can also apply this function to values of type Integer (or any other subtype of Real). This replacement of wider (ancestor) types with narrower (descendant) types is called covariance. The concept of covariance allows us to write generic code and is invaluable when reasoning about inheritance in object-oriented programming languages and polymorphism in functional languages.
However, I also saw something from somewhere else:
scala> class Animal
defined class Animal
scala> class Dog extends Animal
defined class Dog
scala> class Beagle extends Dog
defined class Beagle
scala> def foo(x: List[Dog]) = x
foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it
scala> val an: List[Animal] = foo(List(new Beagle))
an: List[Animal] = List(Beagle#284a6c0)
Parameter x of foo is contravariant; it expects an argument of type List[Dog], but we give it a List[Beagle], and that's okay
[What I think is the second example should also prove Covariance. Because from the first example, I learned that "apply this function to values of type Integer (or any other subtype of Real)". So correspondingly, here we apply this function to values of type List[Beagle](or any other subtype of List[Dog]). But to my surprise, the second example proves Cotravariance]
I think two are talking the same thing, but one proves Covariance and the other Contravariance. I also saw this question from SO. However I am still confused. Did I miss something or one of the examples is wrong?
A Good recent article (August 2016) on that topic is "Cheat Codes for Contravariance and Covariance" by Matt Handler.
It starts from the general concept as presented in "Covariance and Contravariance of Hosts and Visitors" and diagram from Andre Tyukin and anoopelias's answer.
And its concludes with:
Here is how to determine if your type ParametricType[T] can/cannot be covariant/contravariant:
A type can be covariant when it does not call methods on the type that it is generic over.
If the type needs to call methods on generic objects that are passed into it, it cannot be covariant.
Archetypal examples:
Seq[+A], Option[+A], Future[+T]
A type can be contravariant when it does call methods on the type that it is generic over.
If the type needs to return values of the type it is generic over, it cannot be contravariant.
Archetypal examples:
`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]`
Regarding contravariance,
Functions are the best example of contravariance
(note that they’re only contravariant on their arguments, and they’re actually covariant on their result).
For example:
class Dachshund(
name: String,
likesFrisbees: Boolean,
val weinerness: Double
) extends Dog(name, likesFrisbees)
def soundCuteness(animal: Animal): Double =
-4.0/animal.sound.length
def weinerosity(dachshund: Dachshund): Double =
dachshund.weinerness * 100.0
def isDogCuteEnough(dog: Dog, f: Dog => Double): Boolean =
f(dog) >= 0.5
Should we be able to pass weinerosity as an argument to isDogCuteEnough? The answer is no, because the function isDogCuteEnough only guarantees that it can pass, at most specific, a Dog to the function f.
When the function f expects something more specific than what isDogCuteEnough can provide, it could attempt to call a method that some Dogs don’t have (like .weinerness on a Greyhound, which is insane).
That you can pass a List[Beagle] to a function expecting a List[Dog] is nothing to do with contravariance of functions, it is still because List is covariant and that List[Beagle] is a List[Dog].
Instead lets say you had a function:
def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int
This function counts all the legs in a list of dogs. It takes a function that accepts a dog and returns an int representing how many legs this dog has.
Furthermore lets say we have a function:
def countLegsOfAnyAnimal(a: Animal): Int
that can count the legs of any animal. We can pass our countLegsOfAnyAnimal function to our countDogsLegs function as the function argument, this is because if this thing can count the legs of any animal, it can count legs of dogs, because dogs are animals, this is because functions are contravariant.
If you look at the definition of Function1 (functions of one parameter), it is
trait Function1[-A, +B]
That is that they are contravariant on their input and covariant on their output. So Function1[Animal,Int] <: Function1[Dog,Int] since Dog <: Animal
Variance is used to indicate subtyping in terms of Containers(eg: List). In most of the languages, if a function requests object of Class Animal, passing any class that inherits Animal(eg: Dog) would be valid. However, in terms of Containers, these need not be valid.
If your function wants Container[A], what are the possible values that can be passed to it? If B extends A and passing Container[B] is valid, then it is Covariant(eg: List[+T]). If, A extends B(the inverse case) and passing Container[B] for Container[A] is valid, then it is Contravariant. Else, it is invariant(which is the default). You could refer to an article where I have tried explaining variances in Scala
https://blog.lakshmirajagopalan.com/posts/variance-in-scala/
I'm trying to do something in Scala that I'm not sure is possible. I'd love some feedback from the community.
Say I have a sealed trait for some 'thing', a few concrete extensions of it, and a generic class which works with some implementation of that trait..
sealed trait Thing
class CoolThing extends Thing
class OtherThing extends Thing
class BoxOfThings[T <: Thing]
Now, I can define another class which handles two 'boxes of things' like so..
class PairOfBoxes(boxOne: BoxOfThings[_ <: Thing], boxTwo: BoxOfThings[_ <: Thing])
However, here it's perfectly fine to create a PairOfBoxes with one box of CoolThings and the other of OtherThings. I would like to declare that boxOne and boxTwo contain the same type of Thing.. is that at all possible?
For example:
// Cool things..
val boxOfCoolThings = new BoxOfThings[CoolThing]
val anotherBoxOfCoolThings = new BoxOfThings[CoolThing]
// Other things..
val boxOfOtherThings = new BoxOfThings[OtherThing]
// A pair of cool boxes, no problem:
new PairOfBoxes(boxOfCoolThings, anotherBoxOfCoolThings)
// A pair of different boxes, compiles but I don't want it to:
new PairOfBoxes(boxOfOtherThings, anotherBoxOfCoolThings)
I could do this by making the PairOfBoxes generic itself, like so..
class TypedPairOfBoxes[T <: BoxOfThings[_ <: Thing]](boxOne: T, boxTwo: T)
It works, but it's ugly..
// A pair of cool boxes, no problem:
new TypedPairOfBoxes[BoxOfThings[CoolThing]](boxOfCoolThings, anotherBoxOfCoolThings)
// A pair of different boxes, doesn't compile:
val mixedPair = new TypedPairOfBoxes[BoxOfThings[CoolThing]](boxOfOtherThings, anotherBoxOfCoolThings)
I would like to avoid this is I can. It pushes the problem upstream and forces us to specify the contents of each TypedPairOfBoxes. It would be ideal to simply use an untyped PairOfBoxes which asserts it's parameters are of the same type.
Possible?
Thanks!
You just need to write as:
class TypedPairOfBoxes[T <: Thing](one: BoxOfThings[T], two: BoxOfThings[T])
Then:
scala> new TypedPairOfBoxes(boxOfOtherThings, anotherBoxOfCoolThings)
<console>:15: error: type mismatch;
found : BoxOfThings[OtherThing]
required: BoxOfThings[Thing]
Note: OtherThing <: Thing, but class BoxOfThings is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
new TypedPairOfBoxes(boxOfOtherThings, anotherBoxOfCoolThings)
^
<console>:15: error: type mismatch;
found : BoxOfThings[CoolThing]
required: BoxOfThings[Thing]
Note: CoolThing <: Thing, but class BoxOfThings is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
new TypedPairOfBoxes(boxOfOtherThings, anotherBoxOfCoolThings)
^
scala> new TypedPairOfBoxes(boxOfCoolThings, anotherBoxOfCoolThings)
res3: TypedPairOfBoxes[CoolThing] = TypedPairOfBoxes#2f5e1167
I <3 Scala
Scala can infer the generic types such that I can define an 'ugly' typed class but don't need to specify the specific implementation when I use it.
With this realization, I was able to define a typed class similar to the one in my question above..
class TypedPairOfBoxes[T, BoxOfThings[T <: Thing]](boxOne: BoxOfThings[T], boxTwo: BoxOfThings[T])
.. it looks a bit gnarly, but can be used as simply as this:
// Both boxes contain cool things, no problem:
new TypedPairOfBoxes(boxOfCoolThings, anotherBoxOfCoolThing)
// These boxes contain different things, doesn't compile:
new TypedPairOfBoxes(boxOfOtherThings, anotherBoxOfCoolThing)
Amazing.
Edit:
As #Eastsun demonstrated, the latter part of of the generic definition is unused. So can be written, instead, as:
class TypedPairOfBoxes[T <: Thing](boxOne: BoxOfThings[T], boxTwo: BoxOfThings[T])
This looks very much like Java. But the amazing thing here being that Scala infers the generic types from the parameters.