Does Dotty support refinements? - scala

I am reading in dread what will come with Scala 3, paying particular attention to changes to compound types. They were always somewhat of a hack, so clean, true intersection types are certainly an improvement. I couldn't find though anything about what happens to the actual refinement part of the compound type. I rely heavily in my current project on strongly interwoven types in an attempt to have every returned value be as narrow as possible. So, for example, having
trait Thing { thisThing =>
type A
type B
type C
def something :Thing {
type A = <related to thisThing.A>
type B <: <related to thisThing.B>
type C = <etc>
}
Is it even still possible? How to achieve this goal with the new semantics? As I understand, refinements of abstract types will almost certainly be not allowed, so it will be difficult to have a 'self type' declaration in a root of a type hierarchy:
trait Thing {
type ThisThing <: Thing
type A
type B
def copy(...) :ThisThing { type A = ...; type B = ... }
}
On a somewhat related note, I was looking at structural types. I must say I like how we can dynamic member selection/implementation with static declarations. Is it though possible for abstract classes? Could I write something like:
trait Record(members :(String, Any)*) extends Selectable { ... }
val r = new Record { val name :String; val age :Int }
EDIT: I found the relative bit of documentation and it looks like anonymous Selectable instance are exempt from the rule that the type of an anonymous class is the intersection of its extended types - good.

As I understand, refinements of abstract types will almost certainly be not allowed...
type A <: {
type U
}
trait B {
type T <: A
}
object o extends B {
type T = A { type U = Int }
}
perfectly compiles.
https://scastie.scala-lang.org/Nbz3GxoaTSe3VhXZz406vQ
What is not allowed is type projections of abstract types T#U
trait B {
type T <: A
type V = T#U // B.this.T is not a legal path since it is not a concrete type
}
https://scastie.scala-lang.org/xPylqOOkTPK9CvWyDdLvsA
http://dotty.epfl.ch/docs/reference/dropped-features/type-projection.html
Maybe you meant that
type A {
type U
}
is not parseable. But in Scala 2 it isn't either. Correct is either
trait A {
type U
}
or
type A <: {
type U
}

Related

Composing type-level functions with implicit witnesses

I am experimenting with some rather complex type-level calculations. There, I have some type tags (say, A, B, and C), and functions working on them, which are represented by implicit witnesses with path-dependent result types:
class A
class B
class C
trait F1[T] { type result }
trait F2[T] { type result }
implicit object f1OfA extends F1[A] { type result = B }
implicit object f2OfB extends F2[B] { type result = C }
trait Composed[T] { type result }
In the course of calculations, when "implementing" Composed, I need to make use of the fact that I can, given the above code, in principle transform A to C (in this example, I just need the composition, but actually there's more things involved).
However, I don't know how to express the composition, as I am always limited by the restriction that implicits are not transitively applied; the following code fails with "implicit not found":
implicit def composed1[X](implicit f2DotF1OfX: F2[F1[X]]): Composed[X] =
new Composed[X] { type result = f2DotF1OfX.result }
implicitly[Composed[C]]
What I actually tried to write initially was the following:
implicit def composed2[X](implicit f1OfX: F1[X], f2OfLast: F2[f1OfX.result]): Composed[X] =
new Composed[X] { type result = f2OfLast.result }
This of course failed because there I used f1OfLast in the same parameter list it is defined. If it were not an implicit parameter, I could write
implicit def composed3a[X](f1OfX: F1[X])(f2OfLast: F2[f1OfX.result]): Composed[X] =
new Composed[X] { type result = f2OfLast.result }
But it is not possible to do this using two implicit parameter lists, since they are disallowed by the language.
In short: how can I get a witness for F2[F1[X]] in the above examples? If necessary, I could also change the way of writing type-level functions, but I have not yet found another way to express them.
You use another type parameter and then require f1OfX.result to be that:
implicit def composed4[X, Y](implicit f1OfX: F1[X] { type result = Y },
f2OfLast: F2[Y]): Composed[X] =
new Composed[X] { type result = f2OfLast.result }

What's the rule to implement an method in trait?

I defined a trait:
trait A {
def hello(name:Any):Any
}
Then define a class X to implement it:
class X extends A {
def hello(name:Any): Any = {}
}
It compiled. Then I change the return type in the subclass:
class X extends A {
def hello(name:Any): String = "hello"
}
It also compiled. Then change the parameter type:
class X extends A {
def hello(name:String): Any = {}
}
It can't compiled this time, the error is:
error: class X needs to be abstract, since method hello in trait A of type (name: Any)
Any is not defined
(Note that Any does not match String: class String in package lang is a subclass
of class Any in package scala, but method parameter types must match exactly.)
It seems the parameter should match exactly, but the return type can be a subtype in subclass?
Update: #Mik378, thanks for your answer, but why the following example can't work? I think it doesn't break Liskov:
trait A {
def hello(name:String):Any
}
class X extends A {
def hello(name:Any): Any = {}
}
It's exactly like in Java, to keep Liskov Substitution principle, you can't override a method with a more finegrained parameter.
Indeed, what if your code deals with the A type, referencing an X type under the hood.
According to A, you can pass Any type you want, but B would allow only String.
Therefore => BOOM
Logically, with the same reasonning, a more finegrained return type is allowed since it would be cover whatever the case is by any code dealing with the A class.
You may want to check those parts:
http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Covariant_method_return_type
and
http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Contravariant_method_argument_type
UPDATE----------------
trait A {
def hello(name:String):Any
}
class X extends A {
def hello(name:Any): Any = {}
}
It would act as a perfect overloading, not an overriding.
In Scala, it's possible to have methods with the same name but different parameters:
class X extends A {
def hello(name:String) = "String"
def hello(name:Any) = "Any"
}
This is called method overloading, and mirrors the semantics of Java (although my example is unusual - normally overloaded methods would do roughly the same thing, but with different combinations of parameters).
Your code doesn't compile, because parameter types need to match exactly for overriding to work. Otherwise, it interprets your method as a new method with different parameter types.
However, there is no facility within Scala or Java to allow overloading of return types - overloading only depends on the name and parameter types. With return type overloading it would be impossible to determine which overloaded variant to use in all but the simplest of cases:
class X extends A {
def hello: Any = "World"
def hello: String = "Hello"
def doSomething = {
println(hello.toString) // which hello do we call???
}
}
This is why your first example compiles with no problem - there is no ambiguity about which method you are implementing.
Note for JVM pedants - technically, the JVM does distinguish between methods with different return types. However, Java and Scala are careful to only use this facility as an optimisation, and it is not reflected in the semantics of Java or Scala.
This is off the top of my head, but basically for X.hello to fit the requirements of A.hello, you need for the input of X.hello to be a superclass of A.hello's input (covariance) and for the output of X.hello to be a subclass of A.hello's output(contravariance).
Think of this is a specific case of the following
class A
class A' extends A
class B
class B' extends B
f :: A' -> B
g :: A -> B'
the question is "Can I replace f with g in an expression y=f(x) and still typecheck in the same situations?
In that expression, y is of type B and x is of type A'
In y=f(x) we know that y is of type B and x is of type A'
g(x) is still fine because x is of type A' (thus of type A)
y=g(x) is still fine because g(x) is of type B' (thus of type B)
Actually the easiest way to see this is that y is a subtype of B (i.e. implements at least B), meaning that you can use y as a value of type B. You have to do some mental gymnastics in the other thing.
(I just remember that it's one direction on the input, another on the output, and try it out... it becomes obvious if you think about it).

Scala: Type parameters and inheritance

I'm seeing something I do not understand. I have a hierarchy of (say) Vehicles, a corresponding hierarchy of VehicalReaders, and a VehicleReader object with apply methods:
abstract class VehicleReader[T <: Vehicle] {
...
object VehicleReader {
def apply[T <: Vehicle](vehicleId: Int): VehicleReader[T] = apply(vehicleType(vehicleId))
def apply[T <: Vehicle](vehicleType VehicleType): VehicleReader[T] = vehicleType match {
case VehicleType.Car => new CarReader().asInstanceOf[VehicleReader[T]]
...
Note that when you have more than one apply method, you must specify the return type. I have no issues when there is no need to specify the return type.
The cast (.asInstanceOf[VehicleReader[T]]) is the reason for the question - without it the result is compile errors like:
type mismatch;
found : CarReader
required: VehicleReader[T]
case VehicleType.Car => new CarReader()
^
Related questions:
Why cannot the compiler see a CarReader as a VehicleReader[T]?
What is the proper type parameter and return type to use in this situation?
I suspect the root cause here is that VehicleReader is invariant on its type parameter, but making it covariant does not change the result.
I feel like this should be rather simple (i.e., this is easy to accomplish in Java with wildcards).
The problem has a very simple cause and really doesn't have anything to do with variance. Consider even more simple example:
object Example {
def gimmeAListOf[T]: List[T] = List[Int](10)
}
This snippet captures the main idea of your code. But it is incorrect:
val list = Example.gimmeAListOf[String]
What will be the type of list? We asked gimmeAListOf method specifically for List[String], however, it always returns List[Int](10). Clearly, this is an error.
So, to put it in words, when the method has a signature like method[T]: Example[T] it really declares: "for any type T you give me I will return an instance of Example[T]". Such types are sometimes called 'universally quantified', or simply 'universal'.
However, this is not your case: your function returns specific instances of VehicleReader[T] depending on the value of its parameter, e.g. CarReader (which, I presume, extends VehicleReader[Car]). Suppose I wrote something like:
class House extends Vehicle
val reader = VehicleReader[House](VehicleType.Car)
val house: House = reader.read() // Assuming there is a method VehicleReader[T].read(): T
The compiler will happily compile this, but I will get ClassCastException when this code is executed.
There are two possible fixes for this situation available. First, you can use existential (or existentially quantified) type, which can be though as a more powerful version of Java wildcards:
def apply(vehicleType: VehicleType): VehicleReader[_] = ...
Signature for this function basically reads "you give me a VehicleType and I return to you an instance of VehicleReader for some type". You will have an object of type VehicleReader[_]; you cannot say anything about type of its parameter except that this type exists, that's why such types are called existential.
def apply(vehicleType: VehicleType): VehicleReader[T] forSome {type T} = ...
This is an equivalent definition and it is probably more clear from it why these types have such properties - T type is hidden inside parameter, so you don't know anything about it but that it does exist.
But due to this property of existentials you cannot really obtain any information about real type parameters. You cannot get, say, VehicleReader[Car] out of VehicleReader[_] except via direct cast with asInstanceOf, which is dangerous, unless you store a TypeTag/ClassTag for type parameter in VehicleReader and check it before the cast. This is sometimes (in fact, most of time) unwieldy.
That's where the second option comes to the rescue. There is a clear correspondence between VehicleType and VehicleReader[T] in your code, i.e. when you have specific instance of VehicleType you definitely know concrete T in VehicleReader[T] signature:
VehicleType.Car -> CarReader (<: VehicleReader[Car])
VehicleType.Truck -> TruckReader (<: VehicleReader[Truck])
and so on.
Because of this it makes sense to add type parameter to VehicleType. In this case your method will look like
def apply[T <: Vehicle](vehicleType: VehicleType[T]): VehicleReader[T] = ...
Now input type and output type are directly connected, and the user of this method will be forced to provide a correct instance of VehicleType[T] for that T he wants. This rules out the runtime error I have mentioned earlier.
You will still need asInstanceOf cast though. To avoid casting completely you will have to move VehicleReader instantiation code (e.g. yours new CarReader()) to VehicleType, because the only place where you know real value of VehicleType[T] type parameter is where instances of this type are constructed:
sealed trait VehicleType[T <: Vehicle] {
def newReader: VehicleReader[T]
}
object VehicleType {
case object Car extends VehicleType[Car] {
def newReader = new CarReader
}
// ... and so on
}
Then VehicleReader factory method will then look very clean and be completely typesafe:
object VehicleReader {
def apply[T <: Vehicle](vehicleType: VehicleType[T]) = vehicleType.newReader
}

scala type 'extraction'

This might not be the most correct terminology but what I mean by boxed type is Box[T] for type T. So Option[Int] is a boxed Int.
How might one go about extracting these types? My naive attempt:
//extractor
type X[Box[E]] = E //doesn't compile. E not found
//boxed
type boxed = Option[Int]
//unboxed
type parameter = X[boxed] //this is the syntax I would like to achieve
implicitly[parameter =:= Int] //this should compile
Is there any way to do this? Apart from the Apocalisp blog I have hard time finding instructions on type-level meta-programming in Scala.
I can only imagine two situations. Either you use type parameters, then if you use such a higher-kinded-type, e.g. as argument to a method, you will have its type parameter duplicated in the method generics:
trait Box[E]
def doSomething[X](b: Box[X]) { ... } // parameter re-stated as `X`
or you have type members, then you can refer to them per instance:
trait Box { type E }
def doSomething(b: Box) { type X = b.E }
...or generally
def doSomething(x: Box#E) { ... }
So I think you need to rewrite your question in terms of what you actually want to achieve.

Scala: Building a complex hierarchy of traits and classes

I have posted several questions on SO recently dealing with Scala traits, representation types, member types, manifests, and implicit evidence. Behind these questions is my project to build modeling software for biological protein networks. Despite the immensely helpful answers, which have gotten me closer than I ever could get on my own, I have still not arrived at an solution for my project. A couple of answers have suggested that my design is flawed, which is why the solutions to the Foo-framed questions don't work in practice. Here I am posting a more complicated (but still greatly simplified) version of my problem. My hope is that the problem and solution will be broadly useful for people trying to build complex hierarchies of traits and classes in Scala.
The highest-level class in my project is the biological reaction rule. A rule describes how one or two reactants are transformed by a reaction. Each reactant is a graph that has nodes called monomers and edges that connect between named sites on the monomers. Each site also has a state that it can be in. Edit: The concept of the edges have been removed from the example code because they complicate the example without contributing much to the question. A rule might say something like this: there is one reactant made of monomer A bound to monomer B through sites a1 and b1, respectively; the bond is broken by the rule leaving sites a1 and b1 unbound; simultaneously on monomer A, the state of site a1 is changed from U to P. I would write this as:
A(a1~U-1).B(b1-1) -> A(a1~P) + B(b1)
(Parsing strings like this in Scala was so easy, it made my head spin.) The -1 indicates that bond #1 is between those sites--the number is just a arbitrary label.
Here is what I have so far along with the reasoning for why I added each component. It compiles, but only with gratuitous use of asInstanceOf. How do I get rid of the asInstanceOfs so that the types match?
I represent rules with a basic class:
case class Rule(
reactants: Seq[ReactantGraph], // The starting monomers and edges
producedMonomers: Seq[ProducedMonomer] // Only new monomers go here
) {
// Example method that shows different monomers being combined and down-cast
def combineIntoOneGraph: Graph = {
val all_monomers = reactants.flatMap(_.monomers) ++ producedMonomers
GraphClass(all_monomers)
}
}
The class for graphs GraphClass has type parameters because so that I can put constraints on what kinds of monomers and edges are allowed in a particular graph; for example, there cannot be any ProducedMonomers in the Reactant of a Rule. I would also like to be able to collect all the Monomers of a particular type, say ReactantMonomers. I use type aliases to manage the constraints.
case class GraphClass[
+MonomerType <: Monomer
](
monomers: Seq[MonomerType]
) {
// Methods that demonstrate the need for a manifest on MonomerClass
def justTheProductMonomers: Seq[ProductMonomer] = {
monomers.collect{
case x if isProductMonomer(x) => x.asInstanceOf[ProductMonomer]
}
}
def isProductMonomer(monomer: Monomer): Boolean = (
monomer.manifest <:< manifest[ProductStateSite]
)
}
// The most generic Graph
type Graph = GraphClass[Monomer]
// Anything allowed in a reactant
type ReactantGraph = GraphClass[ReactantMonomer]
// Anything allowed in a product, which I sometimes extract from a Rule
type ProductGraph = GraphClass[ProductMonomer]
The class for monomers MonomerClass has type parameters, as well, so that I can put constraints on the sites; for example, a ConsumedMonomer cannot have a StaticStateSite. Furthermore, I need to collect all the monomers of a particular type to, say, collect all the monomers in a rule that are in the product, so I add a Manifest to each type parameter.
case class MonomerClass[
+StateSiteType <: StateSite : Manifest
](
stateSites: Seq[StateSiteType]
) {
type MyType = MonomerClass[StateSiteType]
def manifest = implicitly[Manifest[_ <: StateSiteType]]
// Method that demonstrates the need for implicit evidence
// This is where it gets bad
def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite](
thisSite: A, // This is a member of this.stateSites
monomer: ReactantMonomer
)(
// Only the sites on ReactantMonomers have the Observed property
implicit evidence: MyType <:< ReactantMonomer
): MyType = {
val new_this = evidence(this) // implicit evidence usually needs some help
monomer.stateSites.find(_.name == thisSite.name) match {
case Some(otherSite) =>
val newSites = stateSites map {
case `thisSite` => (
thisSite.asInstanceOf[StateSiteType with ReactantStateSite]
.createIntersection(otherSite).asInstanceOf[StateSiteType]
)
case other => other
}
copy(stateSites = newSites)
case None => this
}
}
}
type Monomer = MonomerClass[StateSite]
type ReactantMonomer = MonomerClass[ReactantStateSite]
type ProductMonomer = MonomerClass[ProductStateSite]
type ConsumedMonomer = MonomerClass[ConsumedStateSite]
type ProducedMonomer = MonomerClass[ProducedStateSite]
type StaticMonomer = MonomerClass[StaticStateSite]
My current implementation for StateSite does not have type parameters; it is a standard hierarchy of traits, terminating in classes that have a name and some Strings that represent the appropriate state. (Be nice about using strings to hold object states; they are actually name classes in my real code.) One important purpose of these traits is provide functionality that all the subclasses need. Well, isn't that the purpose of all traits. My traits are special in that many of the methods make small changes to a property of the object that is common to all subclasses of the trait and then return a copy. It would be preferable if the return type matched the underlying type of the object. The lame way to do this is to make all the trait methods abstract, and copy the desired methods into all the subclasses. I am unsure of the proper Scala way to do this. Some sources suggest a member type MyType that stores the underlying type (shown here). Other sources suggest a representation type parameter.
trait StateSite {
type MyType <: StateSite
def name: String
}
trait ReactantStateSite extends StateSite {
type MyType <: ReactantStateSite
def observed: Seq[String]
def stateCopy(observed: Seq[String]): MyType
def createIntersection(otherSite: ReactantStateSite): MyType = {
val newStates = observed.intersect(otherSite.observed)
stateCopy(newStates)
}
}
trait ProductStateSite extends StateSite
trait ConservedStateSite extends ReactantStateSite with ProductStateSite
case class ConsumedStateSite(name: String, consumed: Seq[String])
extends ReactantStateSite {
type MyType = ConsumedStateSite
def observed = consumed
def stateCopy(observed: Seq[String]) = copy(consumed = observed)
}
case class ProducedStateSite(name: String, Produced: String)
extends ProductStateSite
case class ChangedStateSite(
name: String,
consumed: Seq[String],
Produced: String
)
extends ConservedStateSite {
type MyType = ChangedStateSite
def observed = consumed
def stateCopy(observed: Seq[String]) = copy(consumed = observed)
}
case class StaticStateSite(name: String, static: Seq[String])
extends ConservedStateSite {
type MyType = StaticStateSite
def observed = static
def stateCopy(observed: Seq[String]) = copy(static = observed)
}
My biggest problems are with methods framed like MonomerClass.replaceSiteWithIntersection. A lot of methods do some complicated search for particular members of the class, then pass those members to other functions where complicated changes are made to them and return a copy, which then replaces the original in a copy of the higher-level object. How should I parameterize methods (or the classes) so that the calls are type safe? Right now I can get the code to compile only with lots of asInstanceOfs everywhere. Scala is particularly unhappy with passing instances of a type or member parameter around because of two main reasons that I can see: (1) the covariant type parameter ends up as input to any method that takes them as input, and (2) it is difficult to convince Scala that a method that returns a copy indeed returns an object with exactly the same type as was put in.
I have undoubtedly left some things that will not be clear to everyone. If there are any details I need to add, or excess details I need to delete, I will try to be quick to clear things up.
Edit
#0__ replaced the replaceSiteWithIntersection with a method that compiled without asInstanceOf. Unfortunately, I can't find a way to call the method without a type error. His code is essentially the first method in this new class for MonomerClass; I added the second method that calls it.
case class MonomerClass[+StateSiteType <: StateSite/* : Manifest*/](
stateSites: Seq[StateSiteType]) {
type MyType = MonomerClass[StateSiteType]
//def manifest = implicitly[Manifest[_ <: StateSiteType]]
def replaceSiteWithIntersection[A <: ReactantStateSite { type MyType = A }]
(thisSite: A, otherMonomer: ReactantMonomer)
(implicit ev: this.type <:< MonomerClass[A])
: MonomerClass[A] = {
val new_this = ev(this)
otherMonomer.stateSites.find(_.name == thisSite.name) match {
case Some(otherSite) =>
val newSites = new_this.stateSites map {
case `thisSite` => thisSite.createIntersection(otherSite)
case other => other
}
copy(stateSites = newSites)
case None => new_this // This throws an exception in the real program
}
}
// Example method that calls the previous method
def replaceSomeSiteOnThisOtherMonomer(otherMonomer: ReactantMonomer)
(implicit ev: MyType <:< ReactantMonomer): MyType = {
// Find a state that is a current member of this.stateSites
// Obviously, a more sophisticated means of selection is actually used
val thisSite = ev(this).stateSites(0)
// I can't get this to compile even with asInstanceOf
replaceSiteWithIntersection(thisSite, otherMonomer)
}
}
I have reduced your problem to traits, and I am starting to understand why you are getting into troubles with casts and abstract types.
What you are actually missing is ad-hoc polymorphism, which you obtain through the following:
- Writing a method with generic signature relying on an implicit of the same generic to delegate the work to
- Making the implicit available only for specific value of that generic parameter, which will turn into a "implicit not found" compile time error when you try to do something illegal.
Let's now look to the problem in order. The first is that the signature of your method is wrong for two reasons:
When replacing a site you want to create a new monomer of the new generic type, much as you do when you add to a collection an object which is a superclass of the existing generic type: you get a new collection whose type parameter is the superclass. You should yield this new Monomer as a result.
You are not sure that the operation will yield a result (in case you can't really replace a state). In such a case the right type it's Option[T]
def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite]
(thisSite: A, monomer: ReactantMonomer): Option[MonomerClass[A]]
If we now look digger in the type errors, we can see that the real type error comes from this method:
thisSite.createIntersection
The reason is simple: it's signature is not coherent with the rest of your types, because it accepts a ReactantSite but you want to call it passing as parameter one of your stateSites (which is of type Seq[StateSiteType] ) but you have no guarantee that
StateSiteType<:<ReactantSite
Now let's see how evidences can help you:
trait Intersector[T] {
def apply(observed: Seq[String]): T
}
trait StateSite {
def name: String
}
trait ReactantStateSite extends StateSite {
def observed: Seq[String]
def createIntersection[A](otherSite: ReactantStateSite)(implicit intersector: Intersector[A]): A = {
val newStates = observed.intersect(otherSite.observed)
intersector(newStates)
}
}
import Monomers._
trait MonomerClass[+StateSiteType <: StateSite] {
val stateSites: Seq[StateSiteType]
def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite](thisSite: A, otherMonomer: ReactantMonomer)(implicit intersector:Intersector[A], ev: StateSiteType <:< ReactantStateSite): Option[MonomerClass[A]] = {
def replaceOrKeep(condition: (StateSiteType) => Boolean)(f: (StateSiteType) => A)(implicit ev: StateSiteType<:<A): Seq[A] = {
stateSites.map {
site => if (condition(site)) f(site) else site
}
}
val reactantSiteToIntersect:Option[ReactantStateSite] = otherMonomer.stateSites.find(_.name == thisSite.name)
reactantSiteToIntersect.map {
siteToReplace =>
val newSites = replaceOrKeep {_ == thisSite } { item => thisSite.createIntersection( ev(item) ) }
MonomerClass(newSites)
}
}
}
object MonomerClass {
def apply[A <: StateSite](sites:Seq[A]):MonomerClass[A] = new MonomerClass[A] {
val stateSites = sites
}
}
object Monomers{
type Monomer = MonomerClass[StateSite]
type ReactantMonomer = MonomerClass[ReactantStateSite]
type ProductMonomer = MonomerClass[ProductStateSite]
type ProducedMonomer = MonomerClass[ProducedStateSite]
}
Please note that this pattern can be used with no special imports if you use in a clever way implicit resolving rules (for example you put your insector in the companion object of Intersector trait, so that it will be automatically resolved).
While this pattern works perfectly, there is a limitation connected to the fact that your solution works only for a specific StateSiteType. Scala collections solve a similar problem adding another implicit, which is call CanBuildFrom. In our case we will call it CanReact
You will have to make your MonomerClass invariant, which might be a problem though (why do you need covariance, however?)
trait CanReact[A, B] {
implicit val intersector: Intersector[B]
def react(a: A, b: B): B
def reactFunction(b:B) : A=>B = react(_:A,b)
}
object CanReact {
implicit def CanReactWithReactantSite[A<:ReactantStateSite](implicit inters: Intersector[A]): CanReact[ReactantStateSite,A] = {
new CanReact[ReactantStateSite,A] {
val intersector = inters
def react(a: ReactantStateSite, b: A) = a.createIntersection(b)
}
}
}
trait MonomerClass[StateSiteType <: StateSite] {
val stateSites: Seq[StateSiteType]
def replaceSiteWithIntersection[A >: StateSiteType <: ReactantStateSite](thisSite: A, otherMonomer: ReactantMonomer)(implicit canReact:CanReact[StateSiteType,A]): Option[MonomerClass[A]] = {
def replaceOrKeep(condition: (StateSiteType) => Boolean)(f: (StateSiteType) => A)(implicit ev: StateSiteType<:<A): Seq[A] = {
stateSites.map {
site => if (condition(site)) f(site) else site
}
}
val reactantSiteToIntersect:Option[ReactantStateSite] = otherMonomer.stateSites.find(_.name == thisSite.name)
reactantSiteToIntersect.map {
siteToReplace =>
val newSites = replaceOrKeep {_ == thisSite } { canReact.reactFunction(thisSite)}
MonomerClass(newSites)
}
}
}
With such an implementation, whenever you want to make the possibility to replace a site with another site of a different type, all you need is to make available new implicit instances of CanReact with different types.
I will conclude with a (I hope) clear explanation of why you should not need covariance.
Let's say you have a Consumer[T] and a Producer[T].
You need covariance when you want to provide to the Consumer[T1] a Producer[T2] where T2<:<T1 . But if you need to use the value produced by T2 inside T1, you can
class ConsumerOfStuff[T <: CanBeContained] {
def doWith(stuff: Stuff[T]) = stuff.t.writeSomething
}
trait CanBeContained {
def writeSomething: Unit
}
class A extends CanBeContained {
def writeSomething = println("hello")
}
class B extends A {
override def writeSomething = println("goodbye")
}
class Stuff[T <: CanBeContained](val t: T)
object VarianceTest {
val stuff1 = new Stuff(new A)
val stuff2 = new Stuff(new B)
val consumerOfStuff = new ConsumerOfStuff[A]
consumerOfStuff.doWith(stuff2)
}
This stuff clearly not compiles:
error: type mismatch; found : Stuff[B] required: Stuff[A] Note: B <:
A, but class Stuff is invariant in type T. You may wish to define T as
+T instead. (SLS 4.5) consumerOfStuff.doWith(stuff2).
But again, this come from a misinterpretation of usage of variance, as How are co- and contra-variance used in designing business applications? Kris Nuttycombe answer explain. If we refactor like the following
class ConsumerOfStuff[T <: CanBeContained] {
def doWith[A<:T](stuff: Stuff[A]) = stuff.t.writeSomething
}
You could see everything compiling fine.
Not an answer, but what I can observe from looking over the question:
I see MonomerClass but not Monomer
My guts say you should avoid manifests when possible, as you have seen they can make things complicated. I don't think you will need them. For example the justTheProductMonomers method in GraphClass – since you have complete control over your class hierarchy, why not add test methods for anything involving runtime checks to Monomer directly? E.g.
trait Monomer {
def productOption: Option[ProductMonomer]
}
then you'll have
def justTheProductMonomers : Seq[ProductMonomer] = monomers.flatMap( _.productOption )
and so forth.
The problem here is that it seems you can have a generic monomer satisfying the product predicate, while you somehow want sub-type ProductMonomer.
The general advise I would give is first to define your matrix of tests that you need to process the rules, and then put those tests as methods into the particular traits, unless you have a flat hierarchy for which you can do pattern matching, which is easier since the disambiguation will appear concentrated at your use site, and not spread across all implementing types.
Also don't try to overdue it with compile-time type constraints. Often it's perfectly fine to have some constraints checked at runtime. That way at least you can construct a fully working system, and then you can try to spot the points where you can convert a runtime check into a compile time check, and decide whether the effort is worth it or not. It is appealing to solve things on the type level in Scala, because of its sophistication, but it also requires the most skills to do it right.
There are multiple problems. First, the whole method is weird: On the one hand you passing in a monomer argument, and if the argument thisState is found, the method has nothing to do with the receiver—then why is this a method in MonomerClass at all and not a "free floating" function—, on the other hand you fall back to returning this if thisSite is not found. Since you originally had also implicit evidence: MyType <:< ReactantMonomer, my guess is the whole monomer argument is obsolete, and you actually wanted to operate on new_this.
A bit of cleanup, forgetting the manifests for the moment, you could have
case class MonomerClass[+StateSiteType <: StateSite, +EdgeSiteType <: EdgeSite](
stateSites: Seq[StateSiteType], edgeSites: Seq[EdgeSiteType]) {
def replaceSiteWithIntersection[A <: ReactantStateSite { type MyType = A }]
(thisSite: A)(implicit ev: this.type <:< MonomerClass[A, ReactantEdgeSite])
: MonomerClass[A, ReactantEdgeSite] = {
val monomer = ev(this)
monomer.stateSites.find(_.name == thisSite.name) match {
case Some(otherSite) =>
val newSites = monomer.stateSites map {
case `thisSite` => thisSite.createIntersection(otherSite)
case other => other
}
monomer.copy(stateSites = newSites)
case None => monomer
}
}
}
This was an interesting problem, it took me some iterations to get rid of the (wrong!) casting. Now it is actually quite readable: This method is restricted to the evidence that StateSiteType is actually a subtype A of ReactantStateSite. Therefore, the type parameter A <: ReactantStateSite { type MyType = A }—the last bit is interesting, and this was a new find for myself: You can specify the type member here to make sure that your return type from createIntersection is actually A.
There is still something odd with your method, because if I'm not mistaken, you will end up calling x.createIntersection(x) (intersecting thisSite with itself, which is a no-op).
One thing that is flawed about replaceSiteWithIntersection is that according to the method signature the type of thisSite (A) is a super-type of StateSiteType and a sub-type of ReactantStateSite.
But then you eventually cast it to StateSiteType with ReactantStateSite. That doesn't make sense to me.
Where do you get the assurance from that A suddenly is a StateSiteType?