8

While cracking my head over another question, I came across different riddles which seem related. This is one of them:

trait Sys[S <: Sys[S]] {
  type Peer <: Sys[Peer]
}

trait Fenced {
  type Peer <: Sys[Peer]
}

def makeFence[S <: Sys[S]] = new Fenced { type Peer = S#Peer }

Where the error is as follows:

error: overriding type Peer in trait Fenced with bounds >: Nothing <: Sys[this.Peer];
 type Peer has incompatible type
       def makeFence[S <: Sys[S]] = new Fenced { type Peer = S#Peer }
                                                      ^

Why? (also tried to add self-type _:S => to Sys, didn't matter)


While Rex's answer makes it possible to construct the Fenced object, it does not really solve the issues I have with the representation type character getting lost when using a type projection (S#Peer). I have come up with another scenario which poses harder constraints; I think this is the core issue:

trait Test[S <: Sys[S]] {
  def make[T <: Sys[T]](): Unit

  make[S#Peer]()
}

error: type arguments [S#Peer] do not conform to method make's type 
       parameter bounds [T <: Sys[T]]
              make[S#Peer]()
                  ^
Community
  • 1
  • 1
0__
  • 66,707
  • 21
  • 171
  • 266
  • I am thinking that the fundamental problem is `trait A[B <: Sys[B]]` (which is fine) versus `trait A { type B <: Sys[B] }` (which seems the origin of all trouble). But I really need to work with type members, I cannot introduce type parameters in my case. – 0__ Sep 25 '12 at 19:37
  • What are you trying to accomplish? `S#Peer` is that `Peer` from `S`, but `Fenced` wants the peer to be _its_ `Peer` not `S`'s, which generates the (surface-level) incompatibility. Whether or not it is logically incompatible I guess depends on whether you view types as simple aliases or statements of ownership. Scala is not entirely consistent on this, unfortunately. Are you simply trying to say "`Fenced` has a type that is a `Sys`"? – Rex Kerr Sep 25 '12 at 20:58
  • @RexKerr - sorry if the intention was not clear. The linked questions gives the whole context. Basically, what I (think I) need is to define two linked systems, one referred to by the other, in a way that allows me to pass around the outer system, with no additional information other than `S <: Sys[ S ]` and being able to embed the other peer system fully, using only type members of the outer system. I am kind of hitting the limits of type-projections here. The question tries to illustrate this by saying that it seems impossible to resurrect the peer type within a consumer of the outer system. – 0__ Sep 25 '12 at 21:06

2 Answers2

3

I still am not entirely certain what constraints you're looking for, but here is one possibility:

trait Sys[S <: Sys[S]] {
  type Peer <: Sys[Peer]
}

trait Fenced {
  type MySys <: Sys[MySys]
  type Peer = MySys#Peer
}

def makeFence[S <: Sys[S]] = new Fenced{ type MySys = S }

This gives you (and requires!) access to both Peer and the original outer type within Fenced. I am not sure whether Fenced may do this, or whether it must abstract across outer types.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Very interesting approach, moving type parameter into type member. Yes, there is no problem having both types in `Fenced`. I have to play this through to all consequences, but at least this is a new thought for me, thanks! – 0__ Sep 25 '12 at 21:48
  • This only works with an invariant `S` on `Sys` because it drops the `Peer <: Sys[Peer]` constraint for the `Peer` type member in `Fenced`. – Travis Brown Sep 26 '12 at 00:23
  • Which isn't to say it's not a valid workaround, but in general if you have a `val f = makeFence[X]` with this approach, it won't be the case that `f.Peer <: Sys[f.Peer]` (except when `X` has a concrete `Peer`, of course, but I'm not sure how to phrase that as a constraint). – Travis Brown Sep 26 '12 at 00:30
  • @TravisBrown - Agreed, I assumed that `S` was supposed to be invariant. Also, although being able to create uninhabitable types is disappointing, since you can't create fully actualized types that disobey the `f.Peer <: Sys[f.Peer]` relationship, how would it be a problem (except in warning you further downstream in your code than you'd like that you didn't think things through)? – Rex Kerr Sep 26 '12 at 06:19
  • @RexKerr Thanks for the answer. But can you suggest why did the original question not work and this worked. Aren't they the same (apart from obviously makeing it a member)? – Jatin Jul 10 '14 at 07:07
  • @Jatin - Just because you name things the same way doesn't mean they _are_ the same. This way enforces that things are what you intend them to be. – Rex Kerr Jul 17 '14 at 20:04
3

Can you make Sys's type parameter covariant? For example, this compiles:

trait Sys[+S <: Sys[S]] { type Peer <: Sys[Peer] }
trait Fenced { type Peer <: Sys[Peer] }

def makeFence[S <: Sys[S]] = new Fenced { type Peer = S#Peer }

Now if we have the following (wrapped in an object only for REPL copy-paste convenience):

object Example {
  case class SysX(i: Int) extends Sys[SysX] { type Peer = SysY }
  case class SysY(j: Int) extends Sys[SysY] { type Peer = SysX }
}

import Example._

It works as I'd expect:

scala> val fenceX = makeFence[SysX]
fenceX: java.lang.Object with Fenced{type Peer = Example.SysX#Peer} = ...

scala> val y: fenceX.Peer = SysY(1)
y: fenceX.Peer = SysY(1)

scala> val y: fenceX.Peer = SysX(1)
<console>:15: error: type mismatch;
 found   : Example.SysX
 required: fenceX.Peer
       val y: fenceX.Peer = SysX(1)

Which (I think) is what you want?

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Sorry Travis, but `S` must remain invariant :-( – 0__ Sep 25 '12 at 21:45
  • @Travis Hi Travis. I do not fully understand why the question does not work but I am trying to learn. Can you please explain on why it works if its co-variant and doesn't work if its invariant? – Jatin Jul 10 '14 at 07:12