- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Alternative to builder or trait pattern for inheritable self-copy methods
Thu, 2010-06-10, 17:25
Certain types of self-returning methods can be helpfully and easily made inheritance-friendly by use of this.type:
class FlipFlop {
protected[this] var flipped = false
def flip(): this.type = { flipped = !flipped; this }
def read = flipped
def up(): this.type = if (!flipped) flip() else this
def down(): this.type = if (flipped) flip() else this
}
class CountedFlipFlop extends FlipFlop {
protected[this] var flips = 0
override def flip(): this.type = { flips+=1; flipped = !flipped; this }
def count = flips
}
val cff = new CountedFlipFlop
cff.up.down.flip.count // This works and returns 3 like it should!
Unfortunately, if we try to return anything _except_ "this", the strategy doesn't work--so any sort of copy method is out of bounds.
class FlipFlop {
protected[this] var flipped = false
def flip(): this.type = { flipped = !flipped; this }
def read = flipped
def dup: FlipFlop = {
val ff = new FlipFlop
if (flipped) ff.flip() else ff
}
def opp: FlipFlop = dup.flip()
}
class CountedFlipFlop extends FlipFlop {
/* Rats, now we have to redefine both dup and opp! */
}
What would save extra boilerplate typing is an annotation that might be thought of as this.getClass.type--which I will call it from now on.
class FlipFlop {
...
def dup: this.getClass.type = { /* same as before */ }
def opp: this.getClass.type = dup.flip()
}
Now there's a potential problem. This all typechecks for FlipFlop, but what if we
class CountedFlipFlop extends FlipFlop { }
val cff = (new CountedFlipFlop).dup
Now we're violating our type bounds! dup promises to return a CountedFlipFlop but actually just creates a new FlipFlop!
But the compiler can notice this and flag dup as effectively abstract: you must redefine it so that the return type matches. In that case, the compiler would complain, and you'd have to
class CountedFlipFlop extends FlipFlop {
...
def dup: CountedFlipFlop = {
val cff = new CountedFlipFlop
if (flipped) cff.flip() else cff
}
}
which solves the problem.
Explicitly, the rule is: a return value of this.getClass.type is satisfied by the explicit type of the defining class, but the method is then marked as "must be redefined".
Of course, this doesn't carry the full power of the builder pattern (which can do all this and more), and this would also be largely unnecessary if/when traits get full initializers (as then one could probably achieve the same effect even if you needed extensive variable declaration and initialization).
But this, I suggest, has the virtue of being extremely easy for the user, and I can't see a good way to mimic this with anything Scala already has.
Therefore, I propose that we consider adding "this.getClass.type"-style functionality to Scala. (Exact naming scheme subject to debate; I think "class.type" would be unambiguous and clear and short.)
What do others think?
--Rex
class FlipFlop {
protected[this] var flipped = false
def flip(): this.type = { flipped = !flipped; this }
def read = flipped
def up(): this.type = if (!flipped) flip() else this
def down(): this.type = if (flipped) flip() else this
}
class CountedFlipFlop extends FlipFlop {
protected[this] var flips = 0
override def flip(): this.type = { flips+=1; flipped = !flipped; this }
def count = flips
}
val cff = new CountedFlipFlop
cff.up.down.flip.count // This works and returns 3 like it should!
Unfortunately, if we try to return anything _except_ "this", the strategy doesn't work--so any sort of copy method is out of bounds.
class FlipFlop {
protected[this] var flipped = false
def flip(): this.type = { flipped = !flipped; this }
def read = flipped
def dup: FlipFlop = {
val ff = new FlipFlop
if (flipped) ff.flip() else ff
}
def opp: FlipFlop = dup.flip()
}
class CountedFlipFlop extends FlipFlop {
/* Rats, now we have to redefine both dup and opp! */
}
What would save extra boilerplate typing is an annotation that might be thought of as this.getClass.type--which I will call it from now on.
class FlipFlop {
...
def dup: this.getClass.type = { /* same as before */ }
def opp: this.getClass.type = dup.flip()
}
Now there's a potential problem. This all typechecks for FlipFlop, but what if we
class CountedFlipFlop extends FlipFlop { }
val cff = (new CountedFlipFlop).dup
Now we're violating our type bounds! dup promises to return a CountedFlipFlop but actually just creates a new FlipFlop!
But the compiler can notice this and flag dup as effectively abstract: you must redefine it so that the return type matches. In that case, the compiler would complain, and you'd have to
class CountedFlipFlop extends FlipFlop {
...
def dup: CountedFlipFlop = {
val cff = new CountedFlipFlop
if (flipped) cff.flip() else cff
}
}
which solves the problem.
Explicitly, the rule is: a return value of this.getClass.type is satisfied by the explicit type of the defining class, but the method is then marked as "must be redefined".
Of course, this doesn't carry the full power of the builder pattern (which can do all this and more), and this would also be largely unnecessary if/when traits get full initializers (as then one could probably achieve the same effect even if you needed extensive variable declaration and initialization).
But this, I suggest, has the virtue of being extremely easy for the user, and I can't see a good way to mimic this with anything Scala already has.
Therefore, I propose that we consider adding "this.getClass.type"-style functionality to Scala. (Exact naming scheme subject to debate; I think "class.type" would be unambiguous and clear and short.)
What do others think?
--Rex
Fri, 2010-06-11, 22:17
#2
Re: Alternative to builder or trait pattern for inheritable se
+1This would make things such as lift-mapper require much less boilerplate.
On Thu, Jun 10, 2010 at 12:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On Thu, Jun 10, 2010 at 12:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
--
Paul Phillips | The important thing here is that the music is not in
Stickler | the piano. And knowledge and edification is not in the
Empiricist | computer. The computer is simply an instrument whose
pull his pi pal! | music is ideas. -- Alan Kay
Sun, 2010-06-13, 23:07
#3
Re: Alternative to builder or trait pattern for inheritable se
On Thu, Jun 10, 2010 at 6:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:I am well aware of this previous work. To make it work and useful in contravariant positions, you need to also introduce exact types. So it's a much bigger can of worms than it looks at first.
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
Where you need it, I propose to simulate this feature with an abstract type. They are perfectly adequate, even thought they are a bit more verbose.
Cheers
-- Martin
Sun, 2010-06-13, 23:27
#4
Re: Alternative to builder or trait pattern for inheritable sel
On Sun, Jun 13, 2010 at 11:56:49PM +0200, martin odersky wrote:
> I am well aware of this previous work. To make it work and useful in
> contravariant positions, you need to also introduce exact types. So
> it's a much bigger can of worms than it looks at first.
That's the best part, because then we can run around saying
"exactization" like they do in that paper.
"I find that argument a bit handwavey. Exactizize it a bit."
"This has been overexactizized. Some deexactizization is required."
I think there's also a song to the tune of rod stewart's "Infatuation"
but I don't have the lyrics handy.
Mon, 2010-06-14, 04:37
#5
Re: Alternative to builder or trait pattern for inheritable se
On Sun, Jun 13, 2010 at 5:56 PM, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
Actually, I haven't figured out how to make them adequate, because I don't see any way to get the typing right in a concrete class that you can inherit from. It certainly works with traits, and abstract classes can work also, but you have to maintain an abstract inheritance tree with only concrete leaf nodes (which is kind of annoying for most day-to-day work, though it's no big deal if you're carefully crafting a large library).
Furthermore, I've so far had trouble creating recursive methods (among others) using this scheme, as the compiler notices that T and T#T are not guaranteed to be the same thing under any scheme I've yet been able to cook up.
So despite the large size of the can, I think the worms might be worth it at some point.
--Rex
On Thu, Jun 10, 2010 at 6:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:I am well aware of this previous work. To make it work and useful in contravariant positions, you need to also introduce exact types. So it's a much bigger can of worms than it looks at first.
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
Where you need it, I propose to simulate this feature with an abstract type. They are perfectly adequate, even thought they are a bit more verbose.
Actually, I haven't figured out how to make them adequate, because I don't see any way to get the typing right in a concrete class that you can inherit from. It certainly works with traits, and abstract classes can work also, but you have to maintain an abstract inheritance tree with only concrete leaf nodes (which is kind of annoying for most day-to-day work, though it's no big deal if you're carefully crafting a large library).
Furthermore, I've so far had trouble creating recursive methods (among others) using this scheme, as the compiler notices that T and T#T are not guaranteed to be the same thing under any scheme I've yet been able to cook up.
So despite the large size of the can, I think the worms might be worth it at some point.
--Rex
Mon, 2010-06-14, 08:27
#6
Re: Alternative to builder or trait pattern for inheritable se
Does this mean that Virtual Types are safely on the roadmap then? :)
On 13 June 2010 22:56, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
--
Kevin Wright
mail/google talk: kev [dot] lee [dot] wright [at] gmail [dot] com
wave: kev [dot] lee [dot] wright [at] googlewave [dot] com
skype: kev.lee.wright
twitter: @thecoda
On 13 June 2010 22:56, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
On Thu, Jun 10, 2010 at 6:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:I am well aware of this previous work. To make it work and useful in contravariant positions, you need to also introduce exact types. So it's a much bigger can of worms than it looks at first.
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
Where you need it, I propose to simulate this feature with an abstract type. They are perfectly adequate, even thought they are a bit more verbose.
Cheers
-- Martin
--
Kevin Wright
mail/google talk: kev [dot] lee [dot] wright [at] gmail [dot] com
wave: kev [dot] lee [dot] wright [at] googlewave [dot] com
skype: kev.lee.wright
twitter: @thecoda
Mon, 2010-06-14, 08:57
#7
Re: Alternative to builder or trait pattern for inheritable se
On Mon, Jun 14, 2010 at 9:17 AM, Kevin Wright <kev [dot] lee [dot] wright [at] gmail [dot] com> wrote:
Does this mean that Virtual Types are safely on the roadmap then? :)Scala has virtual types. It's virtual classes that it is missing (in particular the ability to inherit from a virtual class). There have been experiments with virtual classes, and there might be some more, but they are not on the roadmap.
Cheers
-- Martin
On 13 June 2010 22:56, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
On Thu, Jun 10, 2010 at 6:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:I am well aware of this previous work. To make it work and useful in contravariant positions, you need to also introduce exact types. So it's a much bigger can of worms than it looks at first.
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
Where you need it, I propose to simulate this feature with an abstract type. They are perfectly adequate, even thought they are a bit more verbose.
Cheers
-- Martin
--
Kevin Wright
mail/google talk: kev [dot] lee [dot] wright [at] gmail [dot] com
wave: kev [dot] lee [dot] wright [at] googlewave [dot] com
skype: kev.lee.wright
twitter: @thecoda
Mon, 2010-06-14, 09:17
#8
Re: Alternative to builder or trait pattern for inheritable se
D'oh! I should have read that with more care :)
You're quite right that I was thinking of virtual classes...
On 14 June 2010 08:48, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
--
Kevin Wright
mail/google talk: kev [dot] lee [dot] wright [at] gmail [dot] com
wave: kev [dot] lee [dot] wright [at] googlewave [dot] com
skype: kev.lee.wright
twitter: @thecoda
You're quite right that I was thinking of virtual classes...
On 14 June 2010 08:48, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
On Mon, Jun 14, 2010 at 9:17 AM, Kevin Wright <kev [dot] lee [dot] wright [at] gmail [dot] com> wrote:
Does this mean that Virtual Types are safely on the roadmap then? :)Scala has virtual types. It's virtual classes that it is missing (in particular the ability to inherit from a virtual class). There have been experiments with virtual classes, and there might be some more, but they are not on the roadmap.
Cheers
-- Martin
On 13 June 2010 22:56, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
On Thu, Jun 10, 2010 at 6:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:I am well aware of this previous work. To make it work and useful in contravariant positions, you need to also introduce exact types. So it's a much bigger can of worms than it looks at first.
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
Where you need it, I propose to simulate this feature with an abstract type. They are perfectly adequate, even thought they are a bit more verbose.
Cheers
-- Martin
--
Kevin Wright
mail/google talk: kev [dot] lee [dot] wright [at] gmail [dot] com
wave: kev [dot] lee [dot] wright [at] googlewave [dot] com
skype: kev.lee.wright
twitter: @thecoda
--
Kevin Wright
mail/google talk: kev [dot] lee [dot] wright [at] gmail [dot] com
wave: kev [dot] lee [dot] wright [at] googlewave [dot] com
skype: kev.lee.wright
twitter: @thecoda
Mon, 2010-06-14, 17:47
#9
Re: Alternative to builder or trait pattern for inheritable se
For the concrete but inheritable part, use apply functions in a companion object and leave the class abstract (just refine the abstract type member). The virtual constructor can then call the apply function.
Its a well discussed topic on the lists. Its also boilerplate heavy, and doesn't play well with default parameters (compiler can't tell more derived versions apart).
On Jun 14, 2010 5:35 AM, "Rex Kerr" <ichoran [at] gmail [dot] com> wrote:On Sun, Jun 13, 2010 at 5:56 PM, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote: > > On Thu, Jun 10, ...
Actually, I haven't figured out how to make them adequate, because I don't see any way to get the typing right in a concrete class that you can inherit from. It certainly works with traits, and abstract classes can work also, but you have to maintain an abstract inheritance tree with only concrete leaf nodes (which is kind of annoying for most day-to-day work, though it's no big deal if you're carefully crafting a large library).
Furthermore, I've so far had trouble creating recursive methods (among others) using this scheme, as the compiler notices that T and T#T are not guaranteed to be the same thing under any scheme I've yet been able to cook up.
So despite the large size of the can, I think the worms might be worth it at some point.
--Rex
Mon, 2010-06-14, 18:47
#10
Re: Alternative to builder or trait pattern for inheritable se
On Mon, Jun 14, 2010 at 5:34 AM, Rex Kerr <ichoran [at] gmail [dot] com> wrote:
On Sun, Jun 13, 2010 at 5:56 PM, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:The problem is that no scheme will work without exact types, so you need to encode them as well. Here's an encoding of MyType in abstract types that should be faithful.
On Thu, Jun 10, 2010 at 6:40 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:I am well aware of this previous work. To make it work and useful in contravariant positions, you need to also introduce exact types. So it's a much bigger can of worms than it looks at first.
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.
Where you need it, I propose to simulate this feature with an abstract type. They are perfectly adequate, even thought they are a bit more verbose.
Actually, I haven't figured out how to make them adequate, because I don't see any way to get the typing right in a concrete class that you can inherit from. It certainly works with traits, and abstract classes can work also, but you have to maintain an abstract inheritance tree with only concrete leaf nodes (which is kind of annoying for most day-to-day work, though it's no big deal if you're carefully crafting a large library).
Furthermore, I've so far had trouble creating recursive methods (among others) using this scheme, as the compiler notices that T and T#T are not guaranteed to be the same thing under any scheme I've yet been able to cook up.
For every class or trait C, write:
class C {
type MyType <: C
}
For every exact type C write C { type MyType = C }.
Cheers
-- Martin
Mon, 2010-06-14, 20:57
#11
Re: Alternative to builder or trait pattern for inheritable se
I don't think that type MyType <: C works for the things I have in mind, whereas
appropriately-designed MyTypes can. For example, this is perfectly
valid in Scala now:
abstract class Peano {
def succ: this.type
def count(n: Int): this.type = {
if (n<1) this
else if (n==1) succ
else count(n-1).succ
}
}
But if we want to become non-mutable (so that successor could return a
copy), then
abstract class Peano2 {
type MyType <: Peano2
def succ: MyType
def count(n: Int): MyType = {
if (n<1) this // Fails--is of type Peano2
else if (n==1) succ
else count(n-1).succ // Fails--is of type MyType#MyType
}
}
doesn't work at all. The problem is that
MyType <: Peano2
simultaneously allows too much and too little. Too much because we
want MyType#MyType == MyType, but there is no such constraint; and too
little because "this" is not guaranteed to be of type MyType. We
can't increase the constraints, either, or we'll run into problems
when we extend the abstract class.
This seems to me a different issue than the one with exact types and
MyType; in fact, I'm unconvinced that e.g. Kim Bruce has formulated
MyType in the most useful way. The key difference with my proposal
is, I think, that when C has methods with type class.type, then "S
extends C {}" does not necessarily automatically work. Anything built
such that MyType may break on extension must be redefined; thus, some
methods are in a sense _conditionally abstract_. And I'm not certain
that allowing MyType as a method parameter even makes sense; my
reading is that this breaks the Liskov substitution principle and then
one is forced to introduce exact types to rescue the situation.
Thus, I think my original proposal is, though nontrivial to implement,
of enough use to be seriously considered. In this case, it would be
abstract class Peano3 {
def succ: class.type
def count(n: Int): class.type = {
if (n<1) this // Fine, this.type is a subtype of class.type
else if (n==1) succ // Fine, this is class.type also
else count(n-1).succ // Fine, count(n-1) is the same type, as is succ
}
// If succ has the right type, count is guaranteed to have the right type
}
class Peano3B(i: Int) extends Peano3 {
def succ = new Peano3B(i+1) // succ has the right type _for now_ so
this works
}
class Peano3C(val s:String, i: Int) extends Peano3B(i: Int) {
// But now succ has reverted to abstract since 3B's definition only worked
// in the specific case of 3B, not in general regardless of the type of this
def succ = new Peano3C("s("+s+")",i+1) // Must provide this
re-abstracted method
}
This takes care of MyType / class.type as a return value. As a method
argument, it would be much more annoying, because to both obey Liskov
and to satisfy the idea of class.type you'd have to overload the
method:
class C {
def f(c: class.type) = c
}
class S extends C {
def f(c: C) = c
def f(c: class.type) = c
}
which means you'd really have to rewrite code C++ template style in
order to get other methods to use it. It's possible to use exact
types, or to again insist that you can only pass class.type to other
methods that require the parameter, or if you use it some other way
you must rewrite the method. But the consequences of this are likely
to be confusing conceptually, so I'm not sure it's a win overall.
--Rex
On 6/14/10, martin odersky wrote:
> On Mon, Jun 14, 2010 at 5:34 AM, Rex Kerr wrote:
>> I've so far had trouble creating recursive methods (among
>> others) using this scheme, as the compiler notices that T and T#T are not
>> guaranteed to be the same thing under any scheme I've yet been able to
>> cook
>> up.
>>
> The problem is that no scheme will work without exact types, so you need
> to
> encode them as well. Here's an encoding of MyType in abstract types that
> should be faithful.
>
> For every class or trait C, write:
>
> class C {
> type MyType <: C
> }
>
> For every exact type C write C { type MyType = C }.
>
> Cheers
>
Wed, 2010-06-16, 09:07
#12
Re: Alternative to builder or trait pattern for inheritable se
I'll give your proposal some more thought, but in the mean time, here's how you can encode your example right now using type parameters, if that wasn't obvious:
abstract class Peano2[MyType <: Peano2[MyType]] { self: MyType => def succ: MyType def count(n: Int): MyType = { if (n<1) this // self type annotation else if (n==1) succ else count(n-1).succ // f-bounded MyType }}
// once early typedefs actually work, could use type members as type parameters:// abstract class Peano2 extends {x => type MyType <: Peano2{type MyType = x.MyType}} with Object { self: MyType =>
cheersadriaan
On Mon, Jun 14, 2010 at 9:51 PM, Rex Kerr <ichoran [at] gmail [dot] com> wrote:
abstract class Peano2[MyType <: Peano2[MyType]] { self: MyType => def succ: MyType def count(n: Int): MyType = { if (n<1) this // self type annotation else if (n==1) succ else count(n-1).succ // f-bounded MyType }}
// once early typedefs actually work, could use type members as type parameters:// abstract class Peano2 extends {x => type MyType <: Peano2{type MyType = x.MyType}} with Object { self: MyType =>
cheersadriaan
On Mon, Jun 14, 2010 at 9:51 PM, Rex Kerr <ichoran [at] gmail [dot] com> wrote:
I don't think that type MyType <: C works for the things I have in mind, whereas
appropriately-designed MyTypes can. For example, this is perfectly
valid in Scala now:
abstract class Peano {
def succ: this.type
def count(n: Int): this.type = {
if (n<1) this
else if (n==1) succ
else count(n-1).succ
}
}
But if we want to become non-mutable (so that successor could return a
copy), then
abstract class Peano2 {
type MyType <: Peano2
def succ: MyType
def count(n: Int): MyType = {
if (n<1) this // Fails--is of type Peano2
else if (n==1) succ
else count(n-1).succ // Fails--is of type MyType#MyType
}
}
doesn't work at all. The problem is that
MyType <: Peano2
simultaneously allows too much and too little. Too much because we
want MyType#MyType == MyType, but there is no such constraint; and too
little because "this" is not guaranteed to be of type MyType. We
can't increase the constraints, either, or we'll run into problems
when we extend the abstract class.
This seems to me a different issue than the one with exact types and
MyType; in fact, I'm unconvinced that e.g. Kim Bruce has formulated
MyType in the most useful way. The key difference with my proposal
is, I think, that when C has methods with type class.type, then "S
extends C {}" does not necessarily automatically work. Anything built
such that MyType may break on extension must be redefined; thus, some
methods are in a sense _conditionally abstract_. And I'm not certain
that allowing MyType as a method parameter even makes sense; my
reading is that this breaks the Liskov substitution principle and then
one is forced to introduce exact types to rescue the situation.
Thus, I think my original proposal is, though nontrivial to implement,
of enough use to be seriously considered. In this case, it would be
abstract class Peano3 {
def succ: class.type
def count(n: Int): class.type = {
if (n<1) this // Fine, this.type is a subtype of class.type
else if (n==1) succ // Fine, this is class.type also
else count(n-1).succ // Fine, count(n-1) is the same type, as is succ
}
// If succ has the right type, count is guaranteed to have the right type
}
class Peano3B(i: Int) extends Peano3 {
def succ = new Peano3B(i+1) // succ has the right type _for now_ so
this works
}
class Peano3C(val s:String, i: Int) extends Peano3B(i: Int) {
// But now succ has reverted to abstract since 3B's definition only worked
// in the specific case of 3B, not in general regardless of the type of this
def succ = new Peano3C("s("+s+")",i+1) // Must provide this
re-abstracted method
}
This takes care of MyType / class.type as a return value. As a method
argument, it would be much more annoying, because to both obey Liskov
and to satisfy the idea of class.type you'd have to overload the
method:
class C {
def f(c: class.type) = c
}
class S extends C {
def f(c: C) = c
def f(c: class.type) = c
}
which means you'd really have to rewrite code C++ template style in
order to get other methods to use it. It's possible to use exact
types, or to again insist that you can only pass class.type to other
methods that require the parameter, or if you use it some other way
you must rewrite the method. But the consequences of this are likely
to be confusing conceptually, so I'm not sure it's a win overall.
--Rex
On 6/14/10, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
> On Mon, Jun 14, 2010 at 5:34 AM, Rex Kerr <ichoran [at] gmail [dot] com> wrote:
>> I've so far had trouble creating recursive methods (among
>> others) using this scheme, as the compiler notices that T and T#T are not
>> guaranteed to be the same thing under any scheme I've yet been able to
>> cook
>> up.
>>
> The problem is that no scheme will work without exact types, so you need
> to
> encode them as well. Here's an encoding of MyType in abstract types that
> should be faithful.
>
> For every class or trait C, write:
>
> class C {
> type MyType <: C
> }
>
> For every exact type C write C { type MyType = C }.
>
> Cheers
>
> -- Martin
>
Wed, 2010-06-16, 17:37
#13
Re: Alternative to builder or trait pattern for inheritable se
Sorry for being a bit dense in this matter--my intuition for what ends up as generic type parameters and which end up as abstract types is rather poor. Anyway, yes, this does work, and can be extended also:
abstract class Peano2A[MyType <: Peano2A[MyType]] extends Peano2[MyType] {
self: MyType =>
...
}
So as long as one obeys the only-leaf-nodes-are-concrete restriction, this seems to work in all cases where I wanted it to. Thanks!
(I still maintain that restricted MyTypes of the style I proposed would be a lot easier to work with and think about if implemented with the concept of conditional-abstract.)
--Rex
On Wed, Jun 16, 2010 at 4:03 AM, Adriaan Moors <adriaan [dot] moors [at] epfl [dot] ch> wrote:
abstract class Peano2A[MyType <: Peano2A[MyType]] extends Peano2[MyType] {
self: MyType =>
...
}
So as long as one obeys the only-leaf-nodes-are-concrete restriction, this seems to work in all cases where I wanted it to. Thanks!
(I still maintain that restricted MyTypes of the style I proposed would be a lot easier to work with and think about if implemented with the concept of conditional-abstract.)
--Rex
On Wed, Jun 16, 2010 at 4:03 AM, Adriaan Moors <adriaan [dot] moors [at] epfl [dot] ch> wrote:
I'll give your proposal some more thought, but in the mean time, here's how you can encode your example right now using type parameters, if that wasn't obvious:
abstract class Peano2[MyType <: Peano2[MyType]] { self: MyType => def succ: MyType def count(n: Int): MyType = { if (n<1) this // self type annotation else if (n==1) succ else count(n-1).succ // f-bounded MyType }}
// once early typedefs actually work, could use type members as type parameters:// abstract class Peano2 extends {x => type MyType <: Peano2{type MyType = x.MyType}} with Object { self: MyType =>
cheersadriaan
On Mon, Jun 14, 2010 at 9:51 PM, Rex Kerr <ichoran [at] gmail [dot] com> wrote:
I don't think that type MyType <: C works for the things I have in mind, whereas
appropriately-designed MyTypes can. For example, this is perfectly
valid in Scala now:
abstract class Peano {
def succ: this.type
def count(n: Int): this.type = {
if (n<1) this
else if (n==1) succ
else count(n-1).succ
}
}
But if we want to become non-mutable (so that successor could return a
copy), then
abstract class Peano2 {
type MyType <: Peano2
def succ: MyType
def count(n: Int): MyType = {
if (n<1) this // Fails--is of type Peano2
else if (n==1) succ
else count(n-1).succ // Fails--is of type MyType#MyType
}
}
doesn't work at all. The problem is that
MyType <: Peano2
simultaneously allows too much and too little. Too much because we
want MyType#MyType == MyType, but there is no such constraint; and too
little because "this" is not guaranteed to be of type MyType. We
can't increase the constraints, either, or we'll run into problems
when we extend the abstract class.
This seems to me a different issue than the one with exact types and
MyType; in fact, I'm unconvinced that e.g. Kim Bruce has formulated
MyType in the most useful way. The key difference with my proposal
is, I think, that when C has methods with type class.type, then "S
extends C {}" does not necessarily automatically work. Anything built
such that MyType may break on extension must be redefined; thus, some
methods are in a sense _conditionally abstract_. And I'm not certain
that allowing MyType as a method parameter even makes sense; my
reading is that this breaks the Liskov substitution principle and then
one is forced to introduce exact types to rescue the situation.
Thus, I think my original proposal is, though nontrivial to implement,
of enough use to be seriously considered. In this case, it would be
abstract class Peano3 {
def succ: class.type
def count(n: Int): class.type = {
if (n<1) this // Fine, this.type is a subtype of class.type
else if (n==1) succ // Fine, this is class.type also
else count(n-1).succ // Fine, count(n-1) is the same type, as is succ
}
// If succ has the right type, count is guaranteed to have the right type
}
class Peano3B(i: Int) extends Peano3 {
def succ = new Peano3B(i+1) // succ has the right type _for now_ so
this works
}
class Peano3C(val s:String, i: Int) extends Peano3B(i: Int) {
// But now succ has reverted to abstract since 3B's definition only worked
// in the specific case of 3B, not in general regardless of the type of this
def succ = new Peano3C("s("+s+")",i+1) // Must provide this
re-abstracted method
}
This takes care of MyType / class.type as a return value. As a method
argument, it would be much more annoying, because to both obey Liskov
and to satisfy the idea of class.type you'd have to overload the
method:
class C {
def f(c: class.type) = c
}
class S extends C {
def f(c: C) = c
def f(c: class.type) = c
}
which means you'd really have to rewrite code C++ template style in
order to get other methods to use it. It's possible to use exact
types, or to again insist that you can only pass class.type to other
methods that require the parameter, or if you use it some other way
you must rewrite the method. But the consequences of this are likely
to be confusing conceptually, so I'm not sure it's a win overall.
--Rex
On 6/14/10, martin odersky <martin [dot] odersky [at] epfl [dot] ch> wrote:
> On Mon, Jun 14, 2010 at 5:34 AM, Rex Kerr <ichoran [at] gmail [dot] com> wrote:
>> I've so far had trouble creating recursive methods (among
>> others) using this scheme, as the compiler notices that T and T#T are not
>> guaranteed to be the same thing under any scheme I've yet been able to
>> cook
>> up.
>>
> The problem is that no scheme will work without exact types, so you need
> to
> encode them as well. Here's an encoding of MyType in abstract types that
> should be faithful.
>
> For every class or trait C, write:
>
> class C {
> type MyType <: C
> }
>
> For every exact type C write C { type MyType = C }.
>
> Cheers
>
> -- Martin
>
On Thu, Jun 10, 2010 at 12:25:51PM -0400, Rex Kerr wrote:
> What would save extra boilerplate typing is an annotation that might
> be thought of as this.getClass.type--which I will call it from now on.
The clearest label for this that I have encountered is "MyType" and I
encourage others to call it that too. (I don't have any opinion on what
syntax it might have.)
Also, googling MyType will reveal useful previous work and also some
discussion in the context of scala.