I would like to move a type parameter to a type member.
This is the starting point which works:
trait Sys[S <: Sys[S]] {
type Tx
type Id <: Identifier[S#Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx): Unit
}
trait Test[S <: Sys[S]] {
def id: S#Id
def dispose()(implicit tx: S#Tx) {
id.dispose()
}
}
What annoys me is that I'm carrying around a type parameter [S <: Sys[S]]
throughout my entire libraries. So what I was thinking is this:
trait Sys {
type S = this.type // ?
type Tx
type Id <: Identifier[S#Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx): Unit
}
trait Test[S <: Sys] {
def id: S#Id
def dispose()(implicit tx: S#Tx) {
id.dispose()
}
}
Which fails... S#Tx
and S#Id
became somehow detached:
error: could not find implicit value for parameter tx: _9.Tx
id.dispose()
^
Any tricks or changes that make it work?
EDIT : To clarify, I am primarily hoping to fix the type S
in Sys
to make it work. There are numerous problems in my case using path-dependent types. To give just one example which reflects the answers of pedrofuria and Owen:
trait Foo[S <: Sys] {
val s: S
def id: s.Id
def dispose()(implicit tx: s.Tx) {
id.dispose()
}
}
trait Bar[S <: Sys] {
val s: S
def id: s.Id
def foo: Foo[S]
def dispose()(implicit tx: s.Tx) {
foo.dispose()
id.dispose()
}
}
<console>:27: error: could not find implicit value for parameter tx: _106.s.Tx
foo.dispose()
^
Try to make that def foo: Foo[s.type]
to give you an idea that this leads nowhere.
I have 2 versions that compile, however I'm not entirely sure either is what you are looking for in your library. (EDIT: This version is inherently flawed, see comments). Here we remove the type parameter S completely from Sys, and continue to use type projections (vs. path dependent types).
In this version, we convert the type parameter to a type member (I'm not entirely sure this is the correct translation), and then use a combination of type refinement and type projections to assure the correct type in Test.
Also notice that we have to use
A#S#Tx
as our type projection for the implicit parameter, which hopefully sheds some light into whyS#Id
andS#Tx
become "detached." In reality, they aren't detached, declaringtype S = this.type
makesS
a singleton type, which then makesS#T
a path dependent type.To be more clear, given
val a: A {type B}
,a.A
is shorthand fora.type#A
. I.e.S#T
is reallythis.type#T
, which is also why simply declaringdef dispose()(implicit tx: S#S#T)
will not work, becauseS#S#T
is a type projection, not a path dependent type as desired, as exemplified above in the answers that required aval s: S
to compile.EDIT: You can remove the parameter on Test as follows:
However this might require a lot of source code modification.
Regardless of if you use type parameters or type members, specifying the type won't just disappear without reworking how types work in your library. I.e., type parameters and abstract type members are equivalent, so it doesn't seem that you can get rid of the type
S <: Sys[S]
entirely.EDIT2: Without using path-dependent types or something along the lines of Duduk's answer, this doesn't seem to be possible. Here is a slight modification to what I already gave that avoids passing around
val s: S
, however it may not be use-able in your library as it requires changingIdentifier[Tx]
to a type member anddef id: S#Id
to aval
in order to expose the path dependent type:This is not so much an answer as a comment on pedrofurla's answer; which I think is correct. Let me explain why.
Scala has this funny thing where, when you write a type member of a class, it essentially creates two different names, one of which belongs to the class, and the other of which belongs to objects of that class. There is some connection between them, namely that the object member type has to be a subtype of the class member type, but in my experience you very rarely want to use this connection; most of the time you should think of them as entirely separate things.
What you really wanted to do here is package up two types so that you can give a name to the pair of them. So I would write
Sys
like:because that says exactly what you want to do, with no magic or fluff: create a type of objects, each of which stores two things, and those things are types (and have some constraints between them).
Then you can write
Test
the way pedrofurla suggestes:Again, only what you need and nothing extra: to create an instance of
Test
, you must supply aSys
, and that instance ofSys
will contain the types thatTest
needs to work with.In other words, sometimes just think of types as regular old values to be packaged up and passed around.
edit:
Scalability (at least in your example, there may be others I haven't thought of) should not be a problem if you again stick to exactly what you need. In your
Foo
/Bar
example,Here, what we really desire of our
foo
is that it'ss.Tx
is the same as ours.Tx
, because what we want to do is use them interchangeably. So, we just require exactly that, and it compiles with no problems.Although this doesn't answer your question (ensuring minimal modification of existing code), here's a thought:
Instead of
Tx
type being a member ofSys
, and being used inIdentifier
, I would, as a starting point, make it a parameter ofSys
, and ensure it is being used in the same way by bothId <: Identifier
andS <: Sys
, like this:This is hardly an improvement in respect to your motivation (
Sys
still has a type parameter), but my next step would be to convertTx
to type member. The only way I could make it work however, without using any sort ofval s: S
trickery (and types based on it) is to:Sys
into two traits, introducingOuterSys
as a holder ofTx
type and everything else (Sys
andIdentifier
as inner traits), and retainingSys
for whatever else it is doing for youTest
trait belong toOuterSys
Here's the code:
So although not really answering your question, or solving your problem, I was hoping it might at least give you guys some idea how to pull this through. Everything else I tried came back at me with compiler shouting for some instance of
S
and expecting a type based on it.EDIT: No real need for splitting
Sys
:Also neglected to mention the obvious - that types depend on
Sys
instance, which I guess makes sense (no sharing of identifiers between systems? transactions maybe?).No need to "test" from within
Sys
instance either, and no need fortype S <: Sys
any more (andtype S = this.type
in MySystem):Here is a version of
Test
that compiles:You absolutely right in saying "S#Tx and S#Id became somehow detached". You can't guarantee that in both S's they are actually the same type, as I understand.