Cannot implement representation type as type member
Asked Answered
C

2

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]()
                  ^
Coaly answered 25/9, 2012 at 18:44 Comment(3)
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.Coaly
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"?Avoid
@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.Coaly
A
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.

Avoid answered 25/9, 2012 at 21:32 Comment(6)
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!Coaly
This only works with an invariant S on Sys because it drops the Peer <: Sys[Peer] constraint for the Peer type member in Fenced.Franzen
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).Franzen
@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)?Avoid
@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)?Kew
@Kew - 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.Avoid
F
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?

Franzen answered 25/9, 2012 at 21:35 Comment(2)
Sorry Travis, but S must remain invariant :-(Coaly
@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?Kew

© 2022 - 2024 — McMap. All rights reserved.