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__
  • 64,257
  • 16
  • 158
  • 253
  • I am thinking that the fundamental problem is `trait A[B <: a="" all="" b="" but="" cannot="" case.="" fine="" i="" in="" introduce="" is="" members="" my="" need="" of="" origin="" parameters="" really="" seems="" sys="" the="" to="" trouble="" type="" versus="" with="" work=""> – 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 <: a="" able="" am="" and="" being="" by="" consumer="" embed="" fully="" here.="" hitting="" i="" illustrate="" impossible="" it="" kind="" limits="" members="" of="" only="" other="" outer="" peer="" question="" resurrect="" s="" saying="" seems="" sys="" system="" system.="" that="" the="" this="" to="" tries="" type="" type-projections="" using="" within=""> – 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
  • 162,454
  • 26
  • 308
  • 402
  • 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 <: constraint="" for="" in="" member="" sys="" the="" type=""> – 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 <: a="" as="" but="" concrete="" constraint="" course="" has="" how="" i="" not="" of="" phrase="" sure="" sys="" that="" to="" when=""> – 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 <: a="" be="" code="" didn="" downstream="" further="" how="" in="" it="" like="" problem="" relationship="" sys="" than="" that="" things="" think="" through="" warning="" would="" you="" your=""> – 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
  • 135,682
  • 12
  • 352
  • 654
  • 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