digitalmars.D - equivariant functions
- Andrei Alexandrescu (46/46) Oct 12 2008 Many functions return one of their parameters regardless of the way it
- Christopher Wright (12/43) Oct 12 2008 All your examples use typeof(something). Is the intent to make this
- Andrei Alexandrescu (8/23) Oct 12 2008 That example does work today. It's covariance of return values and is
- Christopher Wright (7/35) Oct 12 2008 My mistake -- I could have written it as:
- KennyTM~ (9/79) Oct 12 2008 Sounds good.
- Andrei Alexandrescu (5/90) Oct 12 2008 Yes, incidentally there is a bug in the current compiler (I put it
- KennyTM~ (4/96) Oct 12 2008 Oh this is a bug? o_O I though it is expected.
- Derek Parnell (9/11) Oct 12 2008 LOL. I already thought this was possible in D2. The concept sounds fine ...
- Denis Koroskin (31/46) Oct 12 2008 Sounds more like an easter egg to me.
- Andrei Alexandrescu (4/10) Oct 12 2008 Yes, you didn't write it properly :o). The correct syntax is:
- Denis Koroskin (3/12) Oct 12 2008 Yeah, my bad. But still, you can't argue that typeof(str1) == typeof(str...
- Andrei Alexandrescu (4/11) Oct 12 2008 I prefer my solution because it's more general and addresses more issues...
- Denis Koroskin (8/18) Oct 12 2008 Yes, my solution doesn't address clone() method return type, but I think...
- Denis Koroskin (12/32) Oct 12 2008 And also this:
- Andrei Alexandrescu (14/56) Oct 12 2008 class Storage
- Andrei Alexandrescu (8/31) Oct 12 2008 I just showed how they are related. You can't "think" they aren't
- Jason House (6/51) Oct 12 2008 I don't like how those who are unaware of this feature will misinterpret...
- Walter Bright (16/16) Oct 12 2008 Hmm. Given:
- Andrei Alexandrescu (3/9) Oct 12 2008 int is neither a supertype or a subtype of long.
- Max Samukha (11/20) Oct 12 2008 Are typedefs subtypes of the type they are based on?
- Andrei Alexandrescu (7/23) Oct 12 2008 foo will not pass typechecking. Please note that we haven't discussed
- Denis Koroskin (8/54) Oct 12 2008 Sorry to hit a dead horse but...
- Andrei Alexandrescu (6/14) Oct 12 2008 stripl is three overloads. You must use a precise type on the receiving
- Denis Koroskin (36/52) Oct 12 2008 Ok, I'll try harder. Here is a common used pattern:
- Andrei Alexandrescu (13/58) Oct 12 2008 You mean:
- Andrei Alexandrescu (17/86) Oct 13 2008 Walter found an answer last night.
- KennyTM~ (2/94) Oct 13 2008 What is PassQual!() ?_?
- Andrei Alexandrescu (4/99) Oct 13 2008 Pass the qualifiers of the first argument to the second argument. It's a...
- Sergey Gromov (3/45) Oct 12 2008 I'd use it.
- Christian Kamm (9/9) Oct 13 2008 Can you, given an alias to a function that uses this equivariance, creat...
- Andrei Alexandrescu (3/14) Oct 13 2008 I think so. Technically the problem is similar to handling an overloaded...
- ore-sama (5/11) Oct 13 2008 this conflicts with current definition of typeof. Currently
- Andrei Alexandrescu (3/16) Oct 13 2008 That is correct.
- Jason House (2/20) Oct 13 2008 I've never understood the reuse of keywords for new meanings. I much pre...
- Andrei Alexandrescu (14/37) Oct 13 2008 Even in natural language identical words are reused for various
- KennyTM~ (16/58) Oct 13 2008 Yes. That makes foreign English learners confusing the many uses of
- Yigal Chripun (7/70) Oct 13 2008 if I remember correctly, C# allows you to use keywords as identifiers.
- KennyTM~ (8/77) Oct 13 2008 int _if = 5;
- Robert Fraser (26/68) Oct 13 2008 Just read this paper by Herb Sutter last night:
- Andrei Alexandrescu (12/42) Oct 13 2008 I agree. I'd also add that that could backfire as well. "class" being
- Jeff Nowakowski (6/9) Oct 13 2008 Is there some reason why most languages don't use a reserved symbol for
- Bill Baxter (15/24) Oct 13 2008 As someone mentioned, Perl uses the opposite. User identifiers are
- Benji Smith (5/11) Oct 13 2008 The C preprocessor uses # as a prefix for all of its keywords.
- Bill Baxter (11/22) Oct 13 2008 Good point. A sub-language grafted on top of another language...
- Robert Fraser (10/13) Oct 14 2008 C++/CLI adds them in quite a few places as long as they could not be
- Christopher Wright (5/11) Oct 13 2008 When you try to use principles from natural languages in a programming
- Bruno Medeiros (18/60) Oct 18 2008 But this is somewhat unprecedented in D. In other situations of keyword
- Michel Fortin (46/57) Oct 13 2008 I'm not sure why one would one add another syntax feature for this. I
- Andrei Alexandrescu (10/47) Oct 13 2008 High time to change fonts in your newsreader: it's stripL, not
- Jason House (2/71) Oct 13 2008
- Christopher Wright (19/20) Oct 13 2008 That could be changed, but it would require a lot of bookkeeping in
- Jason House (4/10) Oct 13 2008 If a template's arguments can be defined a prior, adding the functions t...
- Christopher Wright (8/19) Oct 13 2008 If the range of possible arguments is not extensible, yes. You can get
- Aarti_pl (39/102) Oct 13 2008 I don't want to push once again my solution for templates from "Uniform
- Andrei Alexandrescu (9/60) Oct 13 2008 It was my first choice. Walter would rather avoid it for implementation
- Aarti_pl (15/34) Oct 13 2008 IMHO it is much more consistent than current situation. When you write
- ore-sama (2/3) Oct 13 2008 I'll appreciate a visually better, easily readable language. We already ...
- Andrei Alexandrescu (3/10) Oct 13 2008 I hear you! There are quite a few things to keep in the air at once.
- Andrei Alexandrescu (58/58) Oct 13 2008 I'm glad there's interest in equivariant functions. Let me share below
- Steven Schveighoffer (25/82) Oct 14 2008 I think there is a problem with a simple typedef. The problem is that a...
- Frits van Bommel (2/21) Oct 15 2008 What does 'return new typeof(a);' do? :)
- ore-sama (2/20) Oct 13 2008 In this example signature (interface) explicitly depends on private fiel...
- Andrei Alexandrescu (3/18) Oct 13 2008 That's a good point.
- Steven Schveighoffer (38/83) Oct 13 2008 I'm glad you are looking into this. This is along the same vein as what...
- Steven Schveighoffer (6/10) Oct 13 2008 d'oh! A valid solution should fail to compile that usage ;)
- Andrei Alexandrescu (9/53) Oct 13 2008 The function doesn't compile per the typechecking I discussed in a
- Steven Schveighoffer (20/75) Oct 14 2008 So in your scheme, the following is true?
- Benji Smith (5/6) Oct 13 2008 It's a lot to swallow all at once, especially if the equivariance
- Andrei Alexandrescu (3/9) Oct 13 2008 Yes.
- Frits van Bommel (10/20) Oct 15 2008 Are you sure about that? How would this work for interfaces and classes
- ore-sama (9/12) Oct 14 2008 What chews now is library's vocabulary:
- KennyTM~ (2/18) Oct 14 2008 that's why namespaces are introduced.
- ore-sama (4/24) Oct 14 2008 How to typecheck it in the case of mutable argument?
- ore-sama (2/7) Oct 14 2008 Another issue with omonymous keywords is simple syntax highlighters can'...
- Steven Schveighoffer (21/24) Oct 14 2008 As another point on this, I think someone else mentioned it, but I can't...
- Andrei Alexandrescu (14/44) Oct 14 2008 Also I guess:
- Steven Schveighoffer (7/51) Oct 14 2008 inout is obsolete in D1 also. The answer to the protest is 'change inou...
- Andrei Alexandrescu (3/50) Oct 14 2008 Accept and return any subtype of Base.
- Steven Schveighoffer (13/63) Oct 14 2008 so in this case, inout is equivalent to inout(Base)? Definitely a nice
- Simen Kjaeraas (12/62) Oct 14 2008 I was first wondering how this would treat things like
- Denis Koroskin (23/87) Oct 14 2008 Yes
- Simen Kjaeraas (10/103) Oct 14 2008 I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, ...
- Denis Koroskin (33/146) Oct 14 2008 Isn't the following example good enough?
- Simen Kjaeraas (15/75) Oct 14 2008 It is, now that I reread it, and see that it's not all A's. :p
- Andrei Alexandrescu (5/10) Oct 14 2008 IMHO we could simplify by discounting min. The major need is to pass the...
- Denis Koroskin (6/15) Oct 14 2008 IMHO, the solution should be consistent and general enough to cover
- Andrei Alexandrescu (4/24) Oct 14 2008 If you can find a solution that is simple and general enough, my hat is
- Denis Koroskin (21/44) Oct 14 2008 We are here to discuss it. I made many suggestions, but I don't know
- Bill Baxter (4/51) Oct 14 2008 .. and inout(B), inout(A) there have that same constancy? Or is their
- Denis Koroskin (65/74) Oct 15 2008 To answer this I need to take a use case and look at a concrete function...
- Steven Schveighoffer (9/77) Oct 15 2008 inout is a type modifier. At the time you call foo, inout(B) and inout(...
- Robert Fraser (5/111) Oct 14 2008 Er.... isn't that the point. If I pass two mutable arguments to min(), I...
- Denis Koroskin (4/28) Oct 14 2008 Yes, it's all about this. My point is that 'inout' keyword is not very
- KennyTM~ (2/57) Oct 14 2008 I'm afraid the meaning of inout here is very unclear without explanation...
- Steven Schveighoffer (10/66) Oct 14 2008 You are correct. It means 'what you send in as input gets returned as
- Denis Koroskin (23/102) Oct 14 2008 I think that basic idea is very good! (for some reason I was bound to
- KennyTM~ (3/121) Oct 14 2008 “same” seems to be a common identifier though...
- Denis Koroskin (6/8) Oct 14 2008 Yeah, I know :)
- Steven Schveighoffer (11/20) Oct 14 2008 I'd say if we were to add a keyword, same would be on the list of choice...
- KennyTM~ (13/25) Oct 14 2008 Googled "filetype:cpp same" and got ≥1 obscure results:
- ore-sama (2/5) Oct 15 2008 google codesearch says that "same" is used in mozilla code and "inout" -...
- KennyTM~ (8/77) Oct 14 2008 Sorry, I can't think of an existing keyword that can clearly qualify the...
- Steven Schveighoffer (29/107) Oct 14 2008 I meant a new keyword. There aren't many existing keywords that I'd wan...
- KennyTM~ (6/127) Oct 14 2008 That could be an advantage. We've different viewpoints :)
- David Gileadi (8/54) Oct 14 2008 FWIW I agree with KennyTM~ here--typeof(s) made sense to me, including
- Denis Koroskin (13/59) Oct 14 2008 Using typeof, expression would get way too complex and involve some magi...
- Christopher Wright (7/15) Oct 14 2008 I can't:
- Andrei Alexandrescu (8/66) Oct 14 2008 I think more explanation is needed for the other case:
- Steven Schveighoffer (8/73) Oct 14 2008 I like this a lot better.
- Andrei Alexandrescu (24/31) Oct 14 2008 Yah there is a recurring problem - we need to find a notation that works...
- Steven Schveighoffer (75/105) Oct 14 2008 Damn, I think there is some confusion here, because we are trying to sol...
- Andrei Alexandrescu (12/17) Oct 14 2008 Base type and supertype are not to be confused. By base type I
- Steven Schveighoffer (13/29) Oct 14 2008 But a type invariant(T) is not a subtype of const(U). How do you solve ...
- Andrei Alexandrescu (6/39) Oct 14 2008 Yah, that's where the PassQual template is needed. I'd agree without joy...
- Robert Fraser (4/7) Oct 14 2008 FWIW, people have done without type eqivariance w/o complaint since
- Denis Koroskin (4/11) Oct 14 2008 People didn't complain that they were nude until someone discovered it :...
- Bill Baxter (8/21) Oct 14 2008 Agreed. There are uses for equivariance if you have it.
- Andrei Alexandrescu (4/24) Oct 14 2008 Yah. What's the name of the feature? I was told a couple of times; I
- Bill Baxter (39/39) Oct 14 2008 I'm looking at all these funky syntaxes with their many unusual
- Steven Schveighoffer (34/72) Oct 14 2008 I'm not a fan of complexity where it is not required.
- Andrei Alexandrescu (11/25) Oct 14 2008 Ugh. This doesn't quite work because it mimics the "true" solution (=
- Bill Baxter (7/32) Oct 14 2008 Ew. Yeh, that is sticky. This will not be that common though. Maybe
- Andrei Alexandrescu (6/28) Oct 14 2008 Too many. I mean most all. Essentially you can only implement identity
- Bill Baxter (8/40) Oct 14 2008 But stripl just needs constness propagation, right? So it's handled
- Steven Schveighoffer (23/47) Oct 14 2008 That would be invalid. You need at least one inout argument that can be...
- Andrei Alexandrescu (9/43) Oct 14 2008 Well this:
- Steven Schveighoffer (6/49) Oct 14 2008 OK, how about this:
- Bill Baxter (45/128) Oct 14 2008 Think of it this way: right now to create variations on const you have
- Steven Schveighoffer (61/147) Oct 15 2008 An example class with 10 accessors (forget about the implementations):
- Andrei Alexandrescu (31/34) Oct 15 2008 The const? has been on the table. It is a fave of mine because it offers...
- Steven Schveighoffer (15/48) Oct 15 2008 I've convinced myself at this point that the const piece has to be separ...
- ore-sama (6/15) Oct 16 2008 try to template this
- ore-sama (22/27) Oct 21 2008 just a thought:
- Andrei Alexandrescu (4/36) Oct 21 2008 Yah that was discussed under the name of PassQual. One problem is
- Yigal Chripun (14/19) Oct 16 2008 here's a stab at a syntax similar to templates:
- Bruno Medeiros (6/28) Oct 18 2008 Wouldn't work prettily with member functions because one can't
- Andrei Alexandrescu (3/29) Oct 18 2008 Yah, one can't do that unless more notation is added...
- Bill Baxter (32/69) Oct 15 2008 Good example.
- Steven Schveighoffer (11/93) Oct 15 2008 lol!
- Andrei Alexandrescu (20/40) Oct 15 2008 At this point Walter will intervene with:
- KennyTM~ (25/70) Oct 15 2008 Huh? But
- Andrei Alexandrescu (3/80) Oct 15 2008 Yah, but it does not make sense to not apply const? to the return value.
- Steven Schveighoffer (14/109) Oct 15 2008 Also still not terrible. Whatever we come up with should be consistent ...
- Bill Baxter (8/48) Oct 15 2008 I was thinking about that too. But I'm not a big fan of blocks that
- Andrei Alexandrescu (7/56) Oct 15 2008 I disagree. A block is a block is a block. Indentation shows it's not
- Bill Baxter (13/71) Oct 15 2008 If the top of the block is off the screen it's not necessarily obvious
- Andrei Alexandrescu (5/7) Oct 15 2008 By the same argument it's not obvious whether you're in a class,
- Bill Baxter (15/22) Oct 15 2008 The fact that I have to keep one thing in mind does not make an excuse
- Bill Baxter (14/52) Oct 14 2008 Actually, let me take a step back from any concrete syntax. What I
- Andrei Alexandrescu (5/13) Oct 14 2008 Well Scala, Eiffel and probably other languages have support for at
- Denis Koroskin (126/261) Oct 14 2008 I think you brought very interesting topic with a second example!
- Sergey Gromov (42/59) Oct 15 2008 This is a very good point. I think these cases are different, too. And...
- Steven Schveighoffer (33/96) Oct 15 2008 Auto-deducing the return type is only half the problem.
- Steven Schveighoffer (14/36) Oct 14 2008 One thing that should be mentioned, my original post implied that the 'b...
- Benji Smith (11/12) Oct 14 2008 I vote "no" on the whole feature.
- Denis Koroskin (91/142) Oct 14 2008 Now that I thought about it a little more (please, see and comment my po...
- Steven Schveighoffer (26/31) Oct 14 2008 No. IClonable should be:
- Benji Smith (19/33) Oct 14 2008 No, b.clone() definitely returns an instance of B. It's purely
- Andrei Alexandrescu (8/11) Oct 14 2008 Please stop perpetuating misunderstanding and apprehension about the
- Benji Smith (49/60) Oct 14 2008 Fair enough. Here are the reasons I describe the const system as "comple...
- Robert Fraser (30/108) Oct 15 2008 In my ideal language, I'd like a const system where instead of the
- Simen Kjaeraas (20/49) Oct 15 2008 Like in current d2, you mean?
- Robert Fraser (6/62) Oct 16 2008 Well, yes and no. That is typeof(a) would be just "foo", there would be
- Gide Nwawudu (16/33) Oct 15 2008 But how would you know that the parameters are not modified? This
- Gide Nwawudu (7/11) Oct 15 2008 On Tue, 14 Oct 2008 22:51:46 -0400, Benji Smith
- Andrei Alexandrescu (91/157) Oct 15 2008 I see where you're coming from. If your opinion is that the const system...
- Benji Smith (112/170) Oct 15 2008 Hey, Andrei!
- Andrei Alexandrescu (37/87) Oct 15 2008 Sure. I suggested that to Walter two years ago, or maybe three. It was
- Benji Smith (7/27) Oct 15 2008 Point taken.
- John Reimer (8/43) Oct 15 2008 Sorry, can't help it:
- Andrei Alexandrescu (4/53) Oct 15 2008 Agreed. I'm telling you: this is so nice, I thought there for a moment
- Sean Kelly (17/51) Oct 15 2008 This has been an absolute godsend for writing D1->D2 portable code. So
- Bruno Medeiros (19/24) Oct 18 2008 What??
- Bill Baxter (4/25) Oct 18 2008 I think you mean "tailconst". The head is not const, the tail is.
- Bruno Medeiros (5/34) Oct 19 2008 Yes, exactly, I meant 'tailconst'.
- ore-sama (4/6) Oct 18 2008 bug in docs. in can't be scope.
- Bruno Medeiros (7/12) Oct 18 2008 Just a minor correction: the proposal was for all function parameters to...
- Bruno Medeiros (16/32) Oct 18 2008 Together with the existing 'string', I've created aliases of my own:
- Steven Schveighoffer (17/35) Oct 14 2008 B is definitely not the same as A. B could have a different implementat...
- Andrei Alexandrescu (11/51) Oct 14 2008 Clone is different in two ways:
- Steven Schveighoffer (36/86) Oct 14 2008 I'm sure it can be done, but I wasn't thinking of that when you were
- Andrei Alexandrescu (4/17) Oct 14 2008 Nah, the whole goal is to have clone() in the base enforce that derivees...
- Denis Koroskin (7/43) Oct 14 2008 Nope, you missed the idea. Was my post too long?
- Benji Smith (3/4) Oct 14 2008 Oops. You're right. Sorry about that :)
- Steven Schveighoffer (6/32) Oct 14 2008 Yes, I did miss the idea. My apologies, I was not looking at equivarian...
Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. Andrei
Oct 12 2008
Andrei Alexandrescu wrote:class A { A clone(); } class B : A { B clone(); }...Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. AndreiAll your examples use typeof(something). Is the intent to make this example work?class A { A clone(); } class B : A { B clone(); }I take it that this would also extend to delegates? class A {} class B : A {} void foo (A delegate (A) dg) { } B bar (B b) { } foo (&bar); If you require "typeof(something)", I'd never use this feature. Otherwise, I'd use it sometimes.
Oct 12 2008
Christopher Wright wrote:All your examples use typeof(something). Is the intent to make this example work? > class A { A clone(); } > class B : A { B clone(); }That example does work today. It's covariance of return values and is only loosely related to this topic.I take it that this would also extend to delegates? class A {} class B : A {} void foo (A delegate (A) dg) { } B bar (B b) { } foo (&bar); If you require "typeof(something)", I'd never use this feature. Otherwise, I'd use it sometimes.The example is wrong because B delegate(B) is not a supertype nor a subtype of A delegate(A). The proposed solution does involve writing typeof as an indication of the need to return the same type as the argument's. Andrei
Oct 12 2008
Andrei Alexandrescu wrote:Christopher Wright wrote:My mistake -- I could have written it as: void foo (A delegate (B) dg) { } B bar (A a) { } foo (&bar); No input that foo can give to the delegate cannot be given to bar, and no output from bar can be unacceptable.All your examples use typeof(something). Is the intent to make this example work? > class A { A clone(); } > class B : A { B clone(); }That example does work today. It's covariance of return values and is only loosely related to this topic.I take it that this would also extend to delegates? class A {} class B : A {} void foo (A delegate (A) dg) { } B bar (B b) { } foo (&bar); If you require "typeof(something)", I'd never use this feature. Otherwise, I'd use it sometimes.The example is wrong because B delegate(B) is not a supertype nor a subtype of A delegate(A). The proposed solution does involve writing typeof as an indication of the need to return the same type as the argument's. Andrei
Oct 12 2008
Andrei Alexandrescu wrote:Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. AndreiSounds good. -- Please resolve ambiguity when s is a global variable. string s; // currently compiles. typeof(s) f(int s) { return typeof(return).init; }
Oct 12 2008
KennyTM~ wrote:Andrei Alexandrescu wrote:Yes, incidentally there is a bug in the current compiler (I put it somewhere in bugzilla a while ago) that prevents it from using parameter names in a typeof expression. AndreiMany functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. AndreiSounds good. -- Please resolve ambiguity when s is a global variable. string s; // currently compiles. typeof(s) f(int s) { return typeof(return).init; }
Oct 12 2008
Andrei Alexandrescu wrote:KennyTM~ wrote:Oh this is a bug? o_O I though it is expected. Anyway this suggestion seems better than taking the type of some global variable.Andrei Alexandrescu wrote:Yes, incidentally there is a bug in the current compiler (I put it somewhere in bugzilla a while ago) that prevents it from using parameter names in a typeof expression. AndreiMany functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. AndreiSounds good. -- Please resolve ambiguity when s is a global variable. string s; // currently compiles. typeof(s) f(int s) { return typeof(return).init; }
Oct 12 2008
On Sun, 12 Oct 2008 14:34:05 -0500, Andrei Alexandrescu wrote:typeof(s) stripl(const(char)[] s);What do you think? I'm almost afraid to post this.LOL. I already thought this was possible in D2. The concept sounds fine but the "typeof(s)" part is a bit clunky. I know we have to tell the compiler about this somehow, but maybe there is a better syntax. -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Oct 12 2008
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); [snip] There's been several proposals in this group on tackling that problem. [snip] I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.Sounds more like an easter egg to me. But that will not work, I'm afraid. Consider the following example: char* strstr(char* str1, const(char)* str2) { return str1; // it's buggy, but who cares? } char* s = "hello".dup.ptr; char* p = strstr(s, "o"); p[0] = 0; printf("%s", s); // prints 'hell' Let's turn it into 'equivariant function': const(char)* strstr(const(char)* str1, const(char)* str2); Do you see the problem already? const(char)* strstr(const(char)* str1, const(char)* str2) { // which one of the two arguments is of dynamic constancy? Both? return str2; // <g> } char* s = "hello".dup.ptr; char* p = strstr(s, "o"); p[0] = 0; // bang! access violation The dynamic constancy attribute should be distiguishable from other attributes like const/invariant. May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }
Oct 12 2008
Denis Koroskin wrote:On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote: Let's turn it into 'equivariant function': const(char)* strstr(const(char)* str1, const(char)* str2); Do you see the problem already?Yes, you didn't write it properly :o). The correct syntax is: typeof(str1) strstr(const(char)* str1, const(char)* str2); Andrei
Oct 12 2008
On Mon, 13 Oct 2008 01:16:16 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Denis Koroskin wrote:Yeah, my bad. But still, you can't argue that typeof(str1) == typeof(str2).On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote: Let's turn it into 'equivariant function': const(char)* strstr(const(char)* str1, const(char)* str2); Do you see the problem already?Yes, you didn't write it properly :o). The correct syntax is: typeof(str1) strstr(const(char)* str1, const(char)* str2); Andrei
Oct 12 2008
Denis Koroskin wrote:May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei
Oct 12 2008
On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Denis Koroskin wrote:Yes, my solution doesn't address clone() method return type, but I think they aren't related. Besides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei
Oct 12 2008
On Mon, 13 Oct 2008 01:27:42 +0400, Denis Koroskin <2korden gmail.com> wrote:On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:And also this: class Storage { sameconst(Foo) foo() sameconst(this) // const, invariant or none { return _foo; } private Foo _foo; } Your version?Denis Koroskin wrote:Yes, my solution doesn't address clone() method return type, but I think they aren't related. Besides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei
Oct 12 2008
Denis Koroskin wrote:On Mon, 13 Oct 2008 01:27:42 +0400, Denis Koroskin <2korden gmail.com> wrote:class Storage { typeof(this._foo) foo() const { return _foo; } } Please note that your version is technically identical to Walter's suggestion: T foo(return const T value); meaning that whatever qualifier value had will be returned back. Your solution is better visually at the obvious cost of adding a keyword, and let me warn again that we can't afford to add one keyword for each problem we solve. I believe it is more fertile to align compiler's view of the code with programmer expectations. AndreiOn Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:And also this: class Storage { sameconst(Foo) foo() sameconst(this) // const, invariant or none { return _foo; } private Foo _foo; } Your version?Denis Koroskin wrote:Yes, my solution doesn't address clone() method return type, but I think they aren't related. Besides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei
Oct 12 2008
Denis Koroskin wrote:On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I just showed how they are related. You can't "think" they aren't related. Feel free to believe they aren't related, but that means you need to work around some facts.Denis Koroskin wrote:Yes, my solution doesn't address clone() method return type, but I think they aren't related.May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }I prefer my solution because it's more general and addresses more issues than passing the qualifier out. AndreiBesides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);This can be easily worked out with a template. There are some limitations of the solution I suggested. If we're able to address most problems, we can leave the occasional straggler to a template solution. Andrei
Oct 12 2008
Andrei Alexandrescu wrote:I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);I don't like how those who are unaware of this feature will misinterpret the meaning of the function signature. What about use as function arguments? I had to refactor a lot of interface-based code because some code needed exact types and the casting to/from interfaces was a significant performance hit.This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. Andrei
Oct 12 2008
Hmm. Given: typeof(s) foo(long s); auto x = foo(1); is x going to be declared as an int? If so, is the cast of the return type of foo() an implicit cast, or an explicit one? If explicit, what about downcast conversions? For example: class A : B { } typeof(a) foo(A a) { return new A(); } auto x = foo(b); If we do an explicit cast, we've got null in x. I suppose that's ok. What about this: typeof(s) foo(const char[] s) { auto x = new char[3]; ...; return x; } invariant(char)[] t; auto s = foo(t); now s will be unsafely typed as invariant(char)[]. Since const cannot be safely cast to invariant or mutable, we might have a serious problem.
Oct 12 2008
Walter Bright wrote:Hmm. Given: typeof(s) foo(long s); auto x = foo(1); is x going to be declared as an int?int is neither a supertype or a subtype of long. Andrei
Oct 12 2008
On Sun, 12 Oct 2008 16:45:49 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Walter Bright wrote:Are typedefs subtypes of the type they are based on? typedef int Bar; typeof(s) foo(Bar s); auto x = foo(1); // is x int? What will be the type of a typeof(s) inside the function? typeof(s) foo(const(char)[] s) { alias typeof(s) T; // what type is T? }Hmm. Given: typeof(s) foo(long s); auto x = foo(1); is x going to be declared as an int?int is neither a supertype or a subtype of long. Andrei
Oct 12 2008
Walter Bright wrote:If explicit, what about downcast conversions? For example: class A : B { } typeof(a) foo(A a) { return new A(); } auto x = foo(b); If we do an explicit cast, we've got null in x. I suppose that's ok.foo will not pass typechecking. Please note that we haven't discussed typechecking yet.What about this: typeof(s) foo(const char[] s) { auto x = new char[3]; ...; return x; } invariant(char)[] t; auto s = foo(t); now s will be unsafely typed as invariant(char)[]. Since const cannot be safely cast to invariant or mutable, we might have a serious problem.That function will not typecheck either. Let's defer the discussion on how to typecheck the function (I have a few good starters) to after we gauge interest in the solution. Andrei
Oct 12 2008
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. AndreiSorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg? string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.
Oct 12 2008
Denis Koroskin wrote:Sorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg?stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andrei
Oct 12 2008
On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Denis Koroskin wrote:Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:Sorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg?stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andreiclass S { typeof(this) clone(); }
Oct 12 2008
Denis Koroskin wrote:Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); }You mean: interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; }class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst }// Yah and hijacks a short and common word. // When will the keyword bleeding stop?class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:My solution doesn't cover this case because the functions do not return an incoming argument. It's a good example. I take a dent. Andreiclass S { typeof(this) clone(); }
Oct 12 2008
Denis Koroskin wrote:On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Walter found an answer last night. interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; } class Foo : IMaybeBar { PassQual!(typeof(this), Bar) isBar() const { return null; } } class Bar : Foo { typeof(this) isBar() const { return this; } } AndreiDenis Koroskin wrote:Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:Sorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg?stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andreiclass S { typeof(this) clone(); }
Oct 13 2008
Andrei Alexandrescu wrote:Denis Koroskin wrote:What is PassQual!() ?_?On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Walter found an answer last night. interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; } class Foo : IMaybeBar { PassQual!(typeof(this), Bar) isBar() const { return null; } } class Bar : Foo { typeof(this) isBar() const { return this; } } AndreiDenis Koroskin wrote:Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:Sorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg?stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andreiclass S { typeof(this) clone(); }
Oct 13 2008
KennyTM~ wrote:Andrei Alexandrescu wrote:Pass the qualifiers of the first argument to the second argument. It's a simple template. AndreiDenis Koroskin wrote:What is PassQual!() ?_?On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Walter found an answer last night. interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; } class Foo : IMaybeBar { PassQual!(typeof(this), Bar) isBar() const { return null; } } class Bar : Foo { typeof(this) isBar() const { return this; } } AndreiDenis Koroskin wrote:Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:Sorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg?stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andreiclass S { typeof(this) clone(); }
Oct 13 2008
Sun, 12 Oct 2008 14:34:05 -0500, Andrei Alexandrescu wrote:I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this.I'd use it.
Oct 12 2008
Can you, given an alias to a function that uses this equivariance, create a forwarder function with the exact same behaviour? I.e. will calls to typeof(return) identity(alias fn)(ParameterTuple!(fn) args) { return fn(args); } or similar work exactly as calls to fn, even though typeof(return) has to be more than just a type? The parameter storage classes ref, out, lazy already make this kind of forwarding very tricky to do right (I think you have to escape to mixins) and I fear this'll make it even more involved.
Oct 13 2008
Christian Kamm wrote:Can you, given an alias to a function that uses this equivariance, create a forwarder function with the exact same behaviour? I.e. will calls to typeof(return) identity(alias fn)(ParameterTuple!(fn) args) { return fn(args); } or similar work exactly as calls to fn, even though typeof(return) has to be more than just a type? The parameter storage classes ref, out, lazy already make this kind of forwarding very tricky to do right (I think you have to escape to mixins) and I fear this'll make it even more involved.I think so. Technically the problem is similar to handling an overloaded fn. Andrei
Oct 13 2008
Andrei Alexandrescu Wrote:typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
ore-sama wrote:Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
Andrei Alexandrescu Wrote:ore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
Jason House wrote:Andrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andreiore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
Andrei Alexandrescu wrote:Jason House wrote:Yes. That makes foreign English learners confusing the many uses of prepositions. I think a programming language should not be like a natural language, but it must be precise enough to reduce ambiguity as much as possible. Overloading keywords with entirely different meaning is no good (scope, static, invariant). (I think this is OK to use typeof in this feature, since you're really getting a type of something.) - BTW, the keyword problem exists because we chose to use /[_a-z]\w*/ to represent identifiers, which coincide with keywords. Perl and PHP, for example, uses /[$ *%][_a-z]\w*/ to represent variables, so the keyword problem can be somewhat diminished. Of course I'm not asking you to adopt this, as I hate those sigils too, but I'm just saying adding keywords can leave user identifiers alone.Andrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andreiore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
KennyTM~ wrote:Andrei Alexandrescu wrote:for example: int if = 5; the tells the parser not to treat "if" as a reserved word. I don't know how much that feature is actually being used but having a way to escape keywords like that also solves the problem.Jason House wrote:Yes. That makes foreign English learners confusing the many uses of prepositions. I think a programming language should not be like a natural language, but it must be precise enough to reduce ambiguity as much as possible. Overloading keywords with entirely different meaning is no good (scope, static, invariant). (I think this is OK to use typeof in this feature, since you're really getting a type of something.) - BTW, the keyword problem exists because we chose to use /[_a-z]\w*/ to represent identifiers, which coincide with keywords. Perl and PHP, for example, uses /[$ *%][_a-z]\w*/ to represent variables, so the keyword problem can be somewhat diminished. Of course I'm not asking you to adopt this, as I hate those sigils too, but I'm just saying adding keywords can leave user identifiers alone.Andrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andreiore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
Yigal Chripun wrote:KennyTM~ wrote:int _if = 5; The problem Andrei stated is not existing keywords chewing the vocabularies I believe, but *new* keywords chewing the vocabularies. But I also think that there are some "universal keywords" that a good programmer should avoid using as an identifier, e.g. "define", "implements" etc. so you're free to use, though it could also be quite subjective.Andrei Alexandrescu wrote:for example: int if = 5; the tells the parser not to treat "if" as a reserved word. I don't know how much that feature is actually being used but having a way to escape keywords like that also solves the problem.Jason House wrote:Yes. That makes foreign English learners confusing the many uses of prepositions. I think a programming language should not be like a natural language, but it must be precise enough to reduce ambiguity as much as possible. Overloading keywords with entirely different meaning is no good (scope, static, invariant). (I think this is OK to use typeof in this feature, since you're really getting a type of something.) - BTW, the keyword problem exists because we chose to use /[_a-z]\w*/ to represent identifiers, which coincide with keywords. Perl and PHP, for example, uses /[$ *%][_a-z]\w*/ to represent variables, so the keyword problem can be somewhat diminished. Of course I'm not asking you to adopt this, as I hate those sigils too, but I'm just saying adding keywords can leave user identifiers alone.Andrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andreiore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
Andrei Alexandrescu wrote:Jason House wrote:Just read this paper by Herb Sutter last night: http://www.gotw.ca/publications/C++CLIRationale.pdf In particular, the Bjarne Stroustrup quote is quite poignant: "" My experience is that people are addicted to keywords for introducing concepts to the point where a concept that doesnt have its own keyword is surprisingly hard to teach. This effect is more important and deep-rooted than peoples vocally expressed dislike for new keywords. Given a choice and time to consider, people invariably choose the new keyword over a clever workaround. B. Stroustrup (D&E, p. 119) "" And Herb himself: "" When a language feature is necessary, programmers strongly prefer keywords. Normally, all C++ keywords are also re- served words, and taking a new one would break code that is already using that word as an identifier (e.g., as a type or variable name). C++/CLI avoids adding reserved words so as to preserve the goal of having pure extensions, but it also recognizes that programmers expect keywords. C++/CLI balances these re- quirements by adding keywords where most are not reserved words and so do not conflict with user identifiers. ""Andrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andreiore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
Robert Fraser wrote:Just read this paper by Herb Sutter last night: http://www.gotw.ca/publications/C++CLIRationale.pdf In particular, the Bjarne Stroustrup quote is quite poignant: "" My experience is that people are addicted to keywords for introducing concepts to the point where a concept that doesnt have its own keyword is surprisingly hard to teach. This effect is more important and deep-rooted than peoples vocally expressed dislike for new keywords. Given a choice and time to consider, people invariably choose the new keyword over a clever workaround. B. Stroustrup (D&E, p. 119) ""I agree. I'd also add that that could backfire as well. "class" being essentially the same deal as "struct" with a different hat has wasted a perfect opportunity to create a really useful distinct concept. Right now it's no more than a source of embarrassment. "typename" is a shame begotten by another shame, the angle brackets. "explicit" is an expensive fix for a minor problem, bringing along with it new oddities and corner-case behaviors that had to be defined.And Herb himself: "" When a language feature is necessary, programmers strongly prefer keywords. Normally, all C++ keywords are also re- served words, and taking a new one would break code that is already using that word as an identifier (e.g., as a type or variable name). C++/CLI avoids adding reserved words so as to preserve the goal of having pure extensions, but it also recognizes that programmers expect keywords. C++/CLI balances these re- quirements by adding keywords where most are not reserved words and so do not conflict with user identifiers. ""I agree. When considering keyword addition, I think we all should think more about adding contextual keywords, which in the grand D tradition are usually defined as keyword(contextual_keyword). Andrei
Oct 13 2008
Andrei Alexandrescu wrote:I agree. When considering keyword addition, I think we all should think more about adding contextual keywords, which in the grand D tradition are usually defined as keyword(contextual_keyword).Is there some reason why most languages don't use a reserved symbol for keywords, like prefixing them with $ or %? Every language runs into the problem of wanting to add new keywords later on -- why don't new languages avoid this old problem? -Jeff
Oct 13 2008
On Tue, Oct 14, 2008 at 12:02 PM, Jeff Nowakowski <jeff dilacero.org> wrote:Andrei Alexandrescu wrote:As someone mentioned, Perl uses the opposite. User identifiers are prefixed with $ or , keywords are bare. I think the reason it's not used more commonly is just that the code starts to look like line noise. I've not seen a language where keywords are prefixed, but now that you mention it, it does kinda make sense. You only expect to have a few dozen keywords, but the number of variables used in any given program will be much much greater. So if you're going to push one or the other into a separate namespace, it seems more logical to do it to the keywords, not the identifiers. I think in Perl's case, Larry Wall thought that making the distinction between scalars ($var) and arrays ( var) would be useful information to always have right in your face like. --bbI agree. When considering keyword addition, I think we all should think more about adding contextual keywords, which in the grand D tradition are usually defined as keyword(contextual_keyword).Is there some reason why most languages don't use a reserved symbol for keywords, like prefixing them with $ or %? Every language runs into the problem of wanting to add new keywords later on -- why don't new languages avoid this old problem?
Oct 13 2008
Bill Baxter wrote:I've not seen a language where keywords are prefixed, but now that you mention it, it does kinda make sense. You only expect to have a few dozen keywords, but the number of variables used in any given program will be much much greater. So if you're going to push one or the other into a separate namespace, it seems more logical to do it to the keywords, not the identifiers.My preference in D would have been for compile-time keywords to use that same convention, rather than all being prefixed with the word "static". --benji
Oct 13 2008
On Tue, Oct 14, 2008 at 1:54 PM, Benji Smith <dlanguage benjismith.net> wrote:Bill Baxter wrote:Good point. A sub-language grafted on top of another language... Text markup languages like HTML/XML etc are like that too, now that I think of it. The keywords aren't prefixed, but they are syntactically separated. Doxygen/JavaDoc/DDoc are other examples. Those param type keywords are all prefixed. LaTeX is like that too.I've not seen a language where keywords are prefixed, but now that you mention it, it does kinda make sense. You only expect to have a few dozen keywords, but the number of variables used in any given program will be much much greater. So if you're going to push one or the other into a separate namespace, it seems more logical to do it to the keywords, not the identifiers.My preference in D would have been for compile-time keywords to use that same convention, rather than all being prefixed with the word "static".under-performers. There was a discussion about reclaiming it maybe over a year ago. I think it involved Andrei, too -- in his first stint on the NG, before his extended leave. --bb
Oct 13 2008
Andrei Alexandrescu wrote:I agree. When considering keyword addition, I think we all should think more about adding contextual keywords, which in the grand D tradition are usually defined as keyword(contextual_keyword).C++/CLI adds them in quite a few places as long as they could not be misinterpreted as identifiers, and still allows them as identifiers in places where identifiers would be expected. Of course, it makes writing a parser a bit harder (but the technique itself doesn't require semantic analysis, so D could use this while maintaining an unambiguous grammar). For example, the "in" keyword can never occur in an identifier position, so it's treated as an identifier if it's in one, and treated as an operator if it's seen in a "for each" loop (another cool syntax in the language to sneak in a keyword -- note the space).
Oct 14 2008
Andrei Alexandrescu wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game.When you try to use principles from natural languages in a programming language, you might end up with Perl. For all of our sakes, be careful. For that matter, what about adding a few synonyms to D? There's always == versus is for primitive value types....
Oct 13 2008
Andrei Alexandrescu wrote:Jason House wrote:But this is somewhat unprecedented in D. In other situations of keyword recycling, the new keyword meaning was usually added in a new context where the previous use of the same keyword was not valid and was totally unrelated. (ie, 'invariant' for example). But this makes new use of typeof in a situation which is very similar to the previous use, but not consistent or orthogonal with such previous use (ie, the behavior is quite different). The only thing that distinguishes the uses is whether the typeof expression references a parameter defined ahead, instead of something which is in scope. This seems confusing to look for. In fact, one could argue that typeof (the normal one) used in a function return type should be able to see the function's parameter, even though syntactically it appears before (semantically it makes more sense to come afterwards). -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DAndrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andreiore-sama wrote:I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.Andrei Alexandrescu Wrote:That is correct. Andreitypeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use.this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 18 2008
On 2008-10-12 15:34:05 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem.I'm not sure why one would one add another syntax feature for this. I mean: we have templates which are capable of this. Sure, templates are instanciated only when needed and that's not what we want, but I think we'd better reuse the syntax we have, adding a modifier and if necessary for forcing one instanciation at the definition site when possible. I'm not sure if this is valid in D2 (I don't have a D2 compiler in front of me right now), but let's say that this makes C any subtype of const(char): C strip1(C : const(char))(C[] s); This template does what you want, except that it's instanciated only at the call site, making it unusable in some situations. What we need is one instanciation dealing with multiple parametrized types. So let's extend the template syntax a little: C strip1(equivariant C : const(char))(C[] s); Hum, what did that change? Well, the new "equivariant" keyword I added imposes new restrictions to the template argument, such as you cannot know the exact type beyond the type restriction in the parameter definition. These restrictions are designed to enable only one instanciation of the template to cover them all. For instance: C strip1(equivariant C : const(char))(C[] s) { C[] s1 = s; // okay, s1 is of the same type. C[] s2 = "abc"; // error, "abc" is invariant(char)[], // incompatible with base type const(char)[] C[] s2 = new C[5]; // can allocate since we know we deal with "char", // whatever constness it has, it'll always be of the same size. return s; // okay, same type. } Reusing the template syntax would avoid adding yet another different but similar syntax for generic functions. Perhaps you find the template syntax overly verbose for this (I do), but then I'd say it's the template syntax that ought to be rethought instead reinventing it in another more limited form for equivariant functions. (Of course we could start from an equivalent function syntax and then extend it to templates later, so I'm not really against what you're exposing here. I only wish we can reach some syntax consistency in the end.) -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Oct 13 2008
Michel Fortin wrote:On 2008-10-12 15:34:05 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:That's a good point.Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem.I'm not sure why one would one add another syntax feature for this. I mean: we have templates which are capable of this. Sure, templates are instanciated only when needed and that's not what we want, but I think we'd better reuse the syntax we have, adding a modifier and if necessary for forcing one instanciation at the definition site when possible.I'm not sure if this is valid in D2 (I don't have a D2 compiler in front of me right now), but let's say that this makes C any subtype of const(char): C strip1(C : const(char))(C[] s);High time to change fonts in your newsreader: it's stripL, not stripOne... :o) Here's a better way to write that template: S stripl(S)(S s) if (is(S : const char[])); meaning S is a subtype of const char[].This template does what you want, except that it's instanciated only at the call site, making it unusable in some situations. What we need is to instanciation dealing with multiple parametrized types. So let's extend the template syntax a little: C strip1(equivariant C : const(char))(C[] s); Hum, what did that change? Well, the new "equivariant" keyword I added imposes new restrictions to the template argument, such as you cannot know the exact type beyond the type restriction in the parameter definition.I think this could work, but then if you take the hit of a new keyword, maybe Denis' solution works even better. It's less verbose and easier to understand. Andrei
Oct 13 2008
Michel Fortin Wrote:On 2008-10-12 15:34:05 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:Templates are non-virtual right nowMany functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem.I'm not sure why one would one add another syntax feature for this. I mean: we have templates which are capable of this. Sure, templates are instanciated only when needed and that's not what we want, but I think we'd better reuse the syntax we have, adding a modifier and if necessary for forcing one instanciation at the definition site when possible.I'm not sure if this is valid in D2 (I don't have a D2 compiler in front of me right now), but let's say that this makes C any subtype of const(char): C strip1(C : const(char))(C[] s); This template does what you want, except that it's instanciated only at the call site, making it unusable in some situations. What we need is one instanciation dealing with multiple parametrized types. So let's extend the template syntax a little: C strip1(equivariant C : const(char))(C[] s); Hum, what did that change? Well, the new "equivariant" keyword I added imposes new restrictions to the template argument, such as you cannot know the exact type beyond the type restriction in the parameter definition. These restrictions are designed to enable only one instanciation of the template to cover them all. For instance: C strip1(equivariant C : const(char))(C[] s) { C[] s1 = s; // okay, s1 is of the same type. C[] s2 = "abc"; // error, "abc" is invariant(char)[], // incompatible with base type const(char)[] C[] s2 = new C[5]; // can allocate since we know we deal with "char", // whatever constness it has, it'll always be of the same size. return s; // okay, same type. } Reusing the template syntax would avoid adding yet another different but similar syntax for generic functions. Perhaps you find the template syntax overly verbose for this (I do), but then I'd say it's the template syntax that ought to be rethought instead reinventing it in another more limited form for equivariant functions. (Of course we could start from an equivalent function syntax and then extend it to templates later, so I'm not really against what you're exposing here. I only wish we can reach some syntax consistency in the end.) -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Oct 13 2008
Jason House wrote:Templates are non-virtual right nowThat could be changed, but it would require a lot of bookkeeping in temporary files (and not object files). And it would interfere with precompiled libraries. Once D gets decent runtime reflection, I'd like to get generics that would pretty much just hide a bunch of casts and reflection calls: generic (T : class) void foo (T thing) { int i = thing.someMethod(); thing.otherMethod (10, true, new Object()); } transforms to: void foo (ClassInfo info, Object thing) { int i = *cast(int*)info.getMethod ("someMethod").invoke!()(thing); info.getMethod ("otherMethod").invoke!(uint, bool, Object)(thing, 10, true, new Object()); } But that's probably a bit much to ask.
Oct 13 2008
Christopher Wright wrote:Jason House wrote:If a template's arguments can be defined a prior, adding the functions to the vtable is easy. That middle ground may be useful, but wouldn't work for everything.Templates are non-virtual right nowThat could be changed, but it would require a lot of bookkeeping in temporary files (and not object files). And it would interfere with precompiled libraries.
Oct 13 2008
Jason House wrote:Christopher Wright wrote:If the range of possible arguments is not extensible, yes. You can get this by deferring the creation of the vtbl until after all templates have been instantiated. Or you can get it by analyzing the template parameters, but that's extremely expensive. Maybe if the parameter is an enum value, or based on constness of a particular type, but that's about it. That would take a lot of development time. I hope that Walter doesn't choose to pursue this in the near future.Jason House wrote:If a template's arguments can be defined a prior, adding the functions to the vtable is easy. That middle ground may be useful, but wouldn't work for everything.Templates are non-virtual right nowThat could be changed, but it would require a lot of bookkeeping in temporary files (and not object files). And it would interfere with precompiled libraries.
Oct 13 2008
I don't want to push once again my solution for templates from "Uniform syntax" thread, but it seems that my solution would work also in this case as it is another type of pattern matching. My comments inlined. Andrei Alexandrescu pisze:Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem.Q(char)[] stripl(Q : Q(char)[] s); It is not template, so it should be putted into "runtime parenthesis". In first case (function taking char[]) Q will be evaluated to nothing and will be skipped.In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type?In this case I would just use typeof(this) class A { typeof(this) clone(); } class B : A { typeof(this) clone(); } I don't see a reason why it should be same as in above case.It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);This solution seems to me quite unclear. I would prefer if something better would be invented.This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s);Q(char)[] stripl(Q : Q(char)[] s); or typeof(s) stripl(Q : Q(char)[] s); No problems here.b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S);Q(S) stripl(S)(Q : Q(S) if(isSomeString!(S)) s); or Q(S) stripl(S)(Q : Q(S) s) if(isSomeString!(S)); or typeof(s) stripl(S)(Q : Q(S) s) if(isSomeString!(S)); (Sorry, I don't accommodate syntax without parenthesis for one parameter templates :-[)c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s);typeof(s.ptr) getpointer(Q : Q(char)[] s); In this case Q doesn't matter, so it is not used in result type. typeof is still useful.d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; }Looks ok.What do you think? I'm almost afraid to post this. AndreiIMHO your solution is almost perfect. I was lacking just a clear definition what can be changed by user. My version is maybe longer, but also more clear about intents. IMHO two versions should be allowed: Q(char)[] stripl(Q : Q(char)[] s); and typeof(s) stripl(Q : Q(char)[] s); Best Regards Marcin Kuszczak (aarti_pl)
Oct 13 2008
Aarti_pl wrote:I don't want to push once again my solution for templates from "Uniform syntax" thread, but it seems that my solution would work also in this case as it is another type of pattern matching. My comments inlined. Andrei Alexandrescu pisze:It was my first choice. Walter would rather avoid it for implementation difficulty reasons.Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem.Q(char)[] stripl(Q : Q(char)[] s);It is not template, so it should be putted into "runtime parenthesis". In first case (function taking char[]) Q will be evaluated to nothing and will be skipped.And that's part of the difficulty: there is one more type of alias, and now it could even expand to nothing.Because you don't want B's implementor to implement clone to return an A. That would be a mistake.In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type?In this case I would just use typeof(this) class A { typeof(this) clone(); } class B : A { typeof(this) clone(); } I don't see a reason why it should be same as in above case.Yah me too. AndreiIt took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);This solution seems to me quite unclear. I would prefer if something better would be invented.
Oct 13 2008
Andrei Alexandrescu pisze:IMHO it is much more consistent than current situation. When you write typeof(this) in class B it should mean B not A (currently when overriding A function with above signature you will get result of type A ), because "this" for B objects is clearly of type B. Additionally such a syntax shows that function participate in overriding, as the result type is same (like in common cases). BTW. I hope that Walter will reconsider more complicated solutions (also my proposal :-) ) because compiler "facade" to user is very important. This is the syntax, which makes working with language pleasure or difficult. It might be worthy to put more time into implementation, but get more flexible and clean solution. BR Marcin Kuszczak (aarti_pl)Because you don't want B's implementor to implement clone to return an A. That would be a mistake.In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type?In this case I would just use typeof(this) class A { typeof(this) clone(); } class B : A { typeof(this) clone(); } I don't see a reason why it should be same as in above case.
Oct 13 2008
Andrei Alexandrescu Wrote:Your solution is better visuallyI'll appreciate a visually better, easily readable language. We already have powerful language with horrible syntax, we don't need one more. :)
Oct 13 2008
ore-sama wrote:Andrei Alexandrescu Wrote:I hear you! There are quite a few things to keep in the air at once. AndreiYour solution is better visuallyI'll appreciate a visually better, easily readable language. We already have powerful language with horrible syntax, we don't need one more. :)
Oct 13 2008
I'm glad there's interest in equivariant functions. Let me share below how I think they can be properly typechecked. The general signature of an equivariant function is: typeof(expression_involving(pk)) fun(T1 p1, ..., Tk pk, ..., Tn pn); The actual notation does not matter for the purpose of typechecking. What the notation means is that fun will accept a subtype of Tk in position pk, call it Uk, and will return type Uk. Of course, for that magic to happen there are a few restrictions that must be met. The simplest way to typecheck fun is by substituting Tk with a typedef for it: typedef Tk __Surrogate; typeof(expression_involving(__Surrogate.init)) fun(T1 p1, ..., __Surrogate pk, ..., Tn pn) { ... typecheck me ... } The typedef was Walter's idea; my idea involved a synthetic subtype. I think the typedef has a lot of appeal. This is because the typedef of Tk is almost like a subtype of Tk: it has Tk's layout trivially prefix it, converts to Tk implicitly, yet it is a distinct type. Let's see this typechecking at work: 1. Accessing a field: typeof(s.ptr) at(const char[] s, uint i) { return s.ptr + i; } Typecheck with a typedef for const char[], yielding: typedef const(char[]) __Surrogate; typeof(__Surrogate.init.ptr) at(__Surrogate s, uint i) { return s.ptr + i; } Pass. 2. Returning (part of) an argument: typeof(s) stripl(const(char)[] s) { uint i; ... return s[i .. $]; } Typecheck like this: typedef const(char)[] __Surrogate; __Surrogate stripl(__Surrogate s) { uint i; ... return s[i .. $]; } Fail. I think that reveals a bug in the compiler. A slice of a typedef'd array should return the same type as the array itself. I'll file a bug. 3. Walter's example: class Base {} class Derived : Base {} typeof(a) foo(Base a) { return new Base; } Typecheck like this: typedef Base __Surrogate; __Surrogate foo(__Surrogate a) { return new Base; } Fail, as it should. Come at it with all you've got. Andrei
Oct 13 2008
"Andrei Alexandrescu" wroteI'm glad there's interest in equivariant functions. Let me share below how I think they can be properly typechecked. The general signature of an equivariant function is: typeof(expression_involving(pk)) fun(T1 p1, ..., Tk pk, ..., Tn pn); The actual notation does not matter for the purpose of typechecking. What the notation means is that fun will accept a subtype of Tk in position pk, call it Uk, and will return type Uk. Of course, for that magic to happen there are a few restrictions that must be met. The simplest way to typecheck fun is by substituting Tk with a typedef for it: typedef Tk __Surrogate; typeof(expression_involving(__Surrogate.init)) fun(T1 p1, ..., __Surrogate pk, ..., Tn pn) { ... typecheck me ... } The typedef was Walter's idea; my idea involved a synthetic subtype. I think the typedef has a lot of appeal. This is because the typedef of Tk is almost like a subtype of Tk: it has Tk's layout trivially prefix it, converts to Tk implicitly, yet it is a distinct type. Let's see this typechecking at work: 1. Accessing a field: typeof(s.ptr) at(const char[] s, uint i) { return s.ptr + i; } Typecheck with a typedef for const char[], yielding: typedef const(char[]) __Surrogate; typeof(__Surrogate.init.ptr) at(__Surrogate s, uint i) { return s.ptr + i; } Pass. 2. Returning (part of) an argument: typeof(s) stripl(const(char)[] s) { uint i; ... return s[i .. $]; } Typecheck like this: typedef const(char)[] __Surrogate; __Surrogate stripl(__Surrogate s) { uint i; ... return s[i .. $]; } Fail. I think that reveals a bug in the compiler. A slice of a typedef'd array should return the same type as the array itself. I'll file a bug. 3. Walter's example: class Base {} class Derived : Base {} typeof(a) foo(Base a) { return new Base; } Typecheck like this: typedef Base __Surrogate; __Surrogate foo(__Surrogate a) { return new Base; } Fail, as it should. Come at it with all you've got.I think there is a problem with a simple typedef. The problem is that all the fields and functions do not have an equivalent typedef, only the outer type has a typedef. What you need is a 'deep' typedef, or one that typedefs all the contained types for all the fields/functions to ensure that a piece of another argument that is not protected by the typeof() construct is not returned. So what we really need is a type modifier, like const. Here is an example: typedef const(char[]) __Surrogate; typeof(__Surrogate.init.ptr) foo(__Surrogate s, uint i, const(char)[] s2) { return s2.ptr + i; } In your proposed notation, this would look like: typeof(s.ptr) foo(const(char)[] s, uint i, const(char)[] s2) This should fail to compile, but it doesn't (tested with 2.019). You can cause problems like this: char[] test = "test".dup; char *testptr = foo(test, 0, "TEST"); Now testptr points to the beginning of the invariant string "TEST". But your usage of typedef is very good! I never thought of it that way when designing the scoped const proposal. BTW, I didn't say it in my other reply, but I would definitely use something like this. -Steve
Oct 14 2008
Andrei Alexandrescu wrote:3. Walter's example: class Base {} class Derived : Base {} typeof(a) foo(Base a) { return new Base; } Typecheck like this: typedef Base __Surrogate; __Surrogate foo(__Surrogate a) { return new Base; } Fail, as it should. Come at it with all you've got.What does 'return new typeof(a);' do? :)
Oct 15 2008
Andrei Alexandrescu Wrote:Denis Koroskin wrote:In this example signature (interface) explicitly depends on private field (implementation). I just tried to imagine how to document its return type.class Storage { sameconst(Foo) foo() sameconst(this) // const, invariant or none { return _foo; } private Foo _foo; } Your version?class Storage { typeof(this._foo) foo() const { return _foo; } }
Oct 13 2008
ore-sama wrote:Andrei Alexandrescu Wrote:That's a good point. AndreiDenis Koroskin wrote:In this example signature (interface) explicitly depends on private field (implementation). I just tried to imagine how to document its return type.class Storage { sameconst(Foo) foo() sameconst(this) // const, invariant or none { return _foo; } private Foo _foo; } Your version?class Storage { typeof(this._foo) foo() const { return _foo; } }
Oct 13 2008
"Andrei Alexandrescu" wroteMany functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this.I'm glad you are looking into this. This is along the same vein as what I called 'scoped const' (see http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed const variance. Just in terms of const, I have a case where your solution doesn't help/work. Easiest case is min/max: typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; return v2;} Now, imagine you call it like this: char[] best = "bb".dup; best = min(aa, "aa"); If the function and usage are allowed to compile, then this results in best, being a char[], to be pointing to an invariant(char)[]. There are two problems that need to be solved here. First, you need another const type. One that is treated as const, but is implicitly castable back to the argument type, and can't be implicitly casted to. That type modifier needs to be perpetuated throughout the function, because you shouldn't be able to return things that didn't originate from the input. If I create a temporary variable, I should have to use this modifier to declare the variable (in my proposal, at the suggestion of Janice, I used 'inout', a dead keyword). This guarantees that your output is a subset of the input. An example of how min looks in my proposal (with the proposed 'inout' keyword): inout(T) min(T)(inout(T) v1, inout(T) v2) The second problem is, you want the return type to be dependent on both v1 and v2. In my proposal, the return type could depend on multiple arguments, and if they varied by constancy, the return type was defaulted to const, as this is the only possible type that anything can be casted to. You might be tempted to do something like this: typeof(v1) min(T)(const(T) v1, typeof(v1) v2) But then if you pass in the mutable version as the first, the function fails to compile in the usage I have above. In terms of the auto casting to derived types (not just const), I have to get my head around it before I can think of possible counter cases, but const is definitely broken by your solution (or at least, the solution doesn't handle all cases). -Steve
Oct 13 2008
"Steven Schveighoffer" wroteYou might be tempted to do something like this: typeof(v1) min(T)(const(T) v1, typeof(v1) v2) But then if you pass in the mutable version as the first, the function fails to compile in the usage I have above.d'oh! A valid solution should fail to compile that usage ;) I meant this usage (which should compile). const(char)[] x = "bb"; x = min("aa", x); -Steve
Oct 13 2008
Steven Schveighoffer wrote:"Andrei Alexandrescu" wrote I'm glad you are looking into this. This is along the same vein as what I called 'scoped const' (see http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed const variance. Just in terms of const, I have a case where your solution doesn't help/work. Easiest case is min/max: typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; return v2;} Now, imagine you call it like this: char[] best = "bb".dup; best = min(aa, "aa"); If the function and usage are allowed to compile, then this results in best, being a char[], to be pointing to an invariant(char)[].The function doesn't compile per the typechecking I discussed in a different post.There are two problems that need to be solved here. First, you need another const type. One that is treated as const, but is implicitly castable back to the argument type, and can't be implicitly casted to. That type modifier needs to be perpetuated throughout the function, because you shouldn't be able to return things that didn't originate from the input. If I create a temporary variable, I should have to use this modifier to declare the variable (in my proposal, at the suggestion of Janice, I used 'inout', a dead keyword). This guarantees that your output is a subset of the input.I think the typedef-based approach could work (and be that type you mention).An example of how min looks in my proposal (with the proposed 'inout' keyword): inout(T) min(T)(inout(T) v1, inout(T) v2) The second problem is, you want the return type to be dependent on both v1 and v2. In my proposal, the return type could depend on multiple arguments, and if they varied by constancy, the return type was defaulted to const, as this is the only possible type that anything can be casted to. You might be tempted to do something like this: typeof(v1) min(T)(const(T) v1, typeof(v1) v2) But then if you pass in the mutable version as the first, the function fails to compile in the usage I have above.My solution does theoretically support multiple types by including them in the typeof expression. Walter, however, mentioned potential difficulties with implementing them. Andrei
Oct 13 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:I read that, but I didn't understand it, I'll go read it again ;)"Andrei Alexandrescu" wrote I'm glad you are looking into this. This is along the same vein as what I called 'scoped const' (see http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed const variance. Just in terms of const, I have a case where your solution doesn't help/work. Easiest case is min/max: typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; return v2;} Now, imagine you call it like this: char[] best = "bb".dup; best = min(aa, "aa"); If the function and usage are allowed to compile, then this results in best, being a char[], to be pointing to an invariant(char)[].The function doesn't compile per the typechecking I discussed in a different post.So in your scheme, the following is true? void foo2(const(char)[] arg); typeof(arg) foo3(const(char)[] arg); typeof(arg1) foo(const(char)[] arg1) { arg1 = "foo"; // fails typeof(arg1) arg1copy = arg1; // ok const(char)[] arg2 = arg1; // ok foo2(arg1); // ok arg1copy = foo3(arg1); // ok return arg1copy; // ok } If that's the case, then the scheme is similar to what I had in mind. I don't really like the typeof(arg1) to use as the type name for temporary variables inside the function, it seems innocuous. Perhaps a special type name can be implicitly declared inside the function?There are two problems that need to be solved here. First, you need another const type. One that is treated as const, but is implicitly castable back to the argument type, and can't be implicitly casted to. That type modifier needs to be perpetuated throughout the function, because you shouldn't be able to return things that didn't originate from the input. If I create a temporary variable, I should have to use this modifier to declare the variable (in my proposal, at the suggestion of Janice, I used 'inout', a dead keyword). This guarantees that your output is a subset of the input.I think the typedef-based approach could work (and be that type you mention).OK, so how does min look in your scheme? -SteveAn example of how min looks in my proposal (with the proposed 'inout' keyword): inout(T) min(T)(inout(T) v1, inout(T) v2) The second problem is, you want the return type to be dependent on both v1 and v2. In my proposal, the return type could depend on multiple arguments, and if they varied by constancy, the return type was defaulted to const, as this is the only possible type that anything can be casted to. You might be tempted to do something like this: typeof(v1) min(T)(const(T) v1, typeof(v1) v2) But then if you pass in the mutable version as the first, the function fails to compile in the usage I have above.My solution does theoretically support multiple types by including them in the typeof expression. Walter, however, mentioned potential difficulties with implementing them.
Oct 14 2008
Andrei Alexandrescu wrote:What do you think? I'm almost afraid to post this.It's a lot to swallow all at once, especially if the equivariance applies to all type/subtype relationships, and not just the mutability of a value. Am I reading that right? --benji
Oct 13 2008
Benji Smith wrote:Andrei Alexandrescu wrote:Yes. AndreiWhat do you think? I'm almost afraid to post this.It's a lot to swallow all at once, especially if the equivariance applies to all type/subtype relationships, and not just the mutability of a value. Am I reading that right?
Oct 13 2008
Andrei Alexandrescu wrote:Benji Smith wrote:Are you sure about that? How would this work for interfaces and classes that implement them? Remember: Interface references currently don't point to the object vtable at the beginning of an object but to the interface vptr inside the object. Unless that was changed/is going to change? (For example, interface references could be implemented as "fat pointers" consisting of { object ptr, interface vptr } instead of the current { pointer to interface vptr }, which could make this easier to implement)Andrei Alexandrescu wrote:Yes.What do you think? I'm almost afraid to post this.It's a lot to swallow all at once, especially if the equivariance applies to all type/subtype relationships, and not just the mutability of a value. Am I reading that right?
Oct 15 2008
Andrei Alexandrescu Wrote:In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols.What chews now is library's vocabulary: class Director{...} class Department { Director Director() //compiler: ^3^ chuuu~ { return director; } }
Oct 14 2008
ore-sama wrote:Andrei Alexandrescu Wrote:that's why namespaces are introduced.In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols.What chews now is library's vocabulary: class Director{...} class Department { Director Director() //compiler: ^3^ chuuu~ { return director; } }
Oct 14 2008
Andrei Alexandrescu Wrote:I'm glad there's interest in equivariant functions. Let me share below how I think they can be properly typechecked. The general signature of an equivariant function is: typeof(expression_involving(pk)) fun(T1 p1, ..., Tk pk, ..., Tn pn); Let's see this typechecking at work: 1. Accessing a field: typeof(s.ptr) at(const char[] s, uint i) { return s.ptr + i; } Typecheck with a typedef for const char[], yielding: typedef const(char[]) __Surrogate; typeof(__Surrogate.init.ptr) at(__Surrogate s, uint i) { return s.ptr + i; } Pass.How to typecheck it in the case of mutable argument? typedef surrogate as mutable type? In all three variants of function only const and/or equivariant methods should be called on surrogated parameter.
Oct 14 2008
Andrei Alexandrescu Wrote:Even in natural language identical words are reused for various different meanings. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it.Another issue with omonymous keywords is simple syntax highlighters can't distinguish attribute from statement and highlight them in different ways: scope, invariant, in.
Oct 14 2008
"Andrei Alexandrescu" wroteI discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive. I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword. -Steve
Oct 14 2008
Steven Schveighoffer wrote:"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o)inout is obsolete in D1 also. The answer to the protest is 'change inout to ref.' At some point, you can make inout invalid in D1, so it doesn't have a double meaning. Not a big deal IMO ;) -Steve
Oct 14 2008
Steven Schveighoffer wrote:"Andrei Alexandrescu" wroteAccept and return any subtype of Base. AndreiSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:so in this case, inout is equivalent to inout(Base)? Definitely a nice idea, since it can always be implied. But this makes it look very weird if the function is a member: class X { inout inout foo(); } Where the first inout is a modifier on the 'this' argument, and the second inout is equivalent to inout(X). Unless Walter is ok with abolishing the ability to put this modifiers at the front of member functions? :D -Steve"Andrei Alexandrescu" wroteAccept and return any subtype of Base.Steven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);
Oct 14 2008
On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Steven Schveighoffer wrote:I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type? Also, this means a maximum of one inout argument type per function, right? As for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough. -- Simen"Andrei Alexandrescu" wroteAccept and return any subtype of Base. AndreiSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);
Oct 14 2008
On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:YesSteven Schveighoffer wrote:I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?"Andrei Alexandrescu" wroteAccept and return any subtype of Base. AndreiSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);Also, this means a maximum of one inout argument type per function, right?No, there were many example of multiple (and even none) inout function arguments.As for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough.My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.
Oct 14 2008
On Tue, 14 Oct 2008 20:57:56 +0200, Denis Koroskin <2korden gmail.com> wrote:On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, inout(MyClass) b).On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:YesSteven Schveighoffer wrote:I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?"Andrei Alexandrescu" wroteAccept and return any subtype of Base. AndreiSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);Also, this means a maximum of one inout argument type per function, right?No, there were many example of multiple (and even none) inout function arguments.I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){} -- SimenAs for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough.My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.
Oct 14 2008
On Tue, 14 Oct 2008 23:12:43 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:On Tue, 14 Oct 2008 20:57:56 +0200, Denis Koroskin <2korden gmail.com> wrote:Isn't the following example good enough? inout(A) min(inout(A1) a1, inout(A2) a2); // 3 different types How about another one: class Foo { inout this(inout(char)[] chars, inout(int)[] ints) { this.chars = chars; this.ints = ints; } char[] chars; int[] ints; } inout(Foo) createFoo(inout(char)[] chars, inout(int)[] ints) { return new Foo(chars, ints); } auto foo = createFoo("Hello!", [1, 1, 2, 3, 5, 8]); // typeof(foo) == invariant(Foo) The basic idea is to get some arrays of different types and return some other (corresponding) object. Their types may be completely unrelated! Also this one: class Foo { inout(Bar) getBar() inout(this) { return bar; } // takes 'this' of type inout(Foo) an returns inout(Bar). Types are unrelated. private Bar bar; }On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, inout(MyClass) b).On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:YesSteven Schveighoffer wrote:I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?"Andrei Alexandrescu" wroteAccept and return any subtype of Base. AndreiSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);Also, this means a maximum of one inout argument type per function, right?No, there were many example of multiple (and even none) inout function arguments.Ouch! This one hurts. Yet it is inconsistent with multiple argument types. Besides, what advantages of it do you see over "inout min(inout(A) a1, inout(A) a2);"?I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}As for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough.My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.
Oct 14 2008
On Tue, 14 Oct 2008 21:30:47 +0200, Denis Koroskin <2korden gmail.com> wrote:On Tue, 14 Oct 2008 23:12:43 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:It is, now that I reread it, and see that it's not all A's. :pOn Tue, 14 Oct 2008 20:57:56 +0200, Denis Koroskin <2korden gmail.com> wrote:Isn't the following example good enough? inout(A) min(inout(A1) a1, inout(A2) a2); // 3 different typesOn Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, inout(MyClass) b).Also, this means a maximum of one inout argument type per function, right?No, there were many example of multiple (and even none) inout function arguments.With my newfound knowledge of this working on multiple argument types, I agree.Ouch! This one hurts. Yet it is inconsistent with multiple argument types.I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}As for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough.My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.Besides, what advantages of it do you see over "inout min(inout(A) a1, inout(A) a2);"?Mainly that it says "the type of a2 is the same as the type of a1", while inout says "the return type is somehow dependant upon the types of a1 and a2" (probably the lowest common denominator). If I feed mutable and invariant data into a function, and can receive either of those back, I expect it to be const. If I want to feed data of the same type/ constancy to a function, and want it to inform me if I'm doing it wrong, I would state that they should be of the same type/constancy, not that the return value is dependant upon both of them. -- Simen
Oct 14 2008
Simen Kjaeraas wrote:I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 14 2008
On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Simen Kjaeraas wrote:IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 14 2008
Denis Koroskin wrote:On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:If you can find a solution that is simple and general enough, my hat is off to you. AndreiSimen Kjaeraas wrote:IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 14 2008
On Wed, 15 Oct 2008 05:30:52 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Denis Koroskin wrote:We are here to discuss it. I made many suggestions, but I don't know whether they fine, too complex or just lame untill someone comments it (interesting enough, some reply without reading). What's wrong with typeof(this) (I just generalized your clone() idea combined with implicit upcasting suggested by Steven)? The "I don't like it" comment would be useful, too. What's wrong with inout/whatever? So far you brought just one 'problematic' example: inout(C) foo(inout(B) function(inout(A)) fn); I believe this is as meaningless as inout(A) foo(); // what does it return? A, const(A) or invariant(A)? and thus should be statically disallowed. I mean, inout(return) doesn't have any sense unless a function accepts some inout(parameter): inout(B) foo(inout(A) a); // ok // your example with an added inout(in) parameter. Now it is fine inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter.On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:If you can find a solution that is simple and general enough, my hat is off to you. AndreiSimen Kjaeraas wrote:IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 14 2008
On Wed, Oct 15, 2008 at 2:31 PM, Denis Koroskin <2korden gmail.com> wrote:On Wed, 15 Oct 2008 05:30:52 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:.. and inout(B), inout(A) there have that same constancy? Or is their constancy unrelated? --bbDenis Koroskin wrote:We are here to discuss it. I made many suggestions, but I don't know whether they fine, too complex or just lame untill someone comments it (interesting enough, some reply without reading). What's wrong with typeof(this) (I just generalized your clone() idea combined with implicit upcasting suggested by Steven)? The "I don't like it" comment would be useful, too. What's wrong with inout/whatever? So far you brought just one 'problematic' example: inout(C) foo(inout(B) function(inout(A)) fn); I believe this is as meaningless as inout(A) foo(); // what does it return? A, const(A) or invariant(A)? and thus should be statically disallowed. I mean, inout(return) doesn't have any sense unless a function accepts some inout(parameter): inout(B) foo(inout(A) a); // ok // your example with an added inout(in) parameter. Now it is fine inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter.On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:If you can find a solution that is simple and general enough, my hat is off to you. AndreiSimen Kjaeraas wrote:IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 14 2008
On Wed, 15 Oct 2008 09:51:59 +0400, Bill Baxter <wbaxter gmail.com> wrote:To answer this I need to take a use case and look at a concrete function implementation: ------- void testInoutFunction(inout(char[]) function(inout(char)[]) fn) { char[] a1 = "hello".dup; a1 = fn(a1); writefln(a1); const(char)[] a2 = "hello"; a2 = b(a2); writefln(a2); invariant(char)[] a3 = "hello"; a3 = b(a3); writefln(a3); } inout(char)[] foo(inout(char)[] a); testInoutFunction(&foo); I think this is a valid use case. Note that testInoutFunction doesn't take any inout() params and the inout() constancy expantion doesn't take place at the call-time, i.e. void testInoutFunction(inout(char[]) function(inout(char)[]) fn) doesn't transform into one of these: void testInoutFunction(char[] function(char[]) fn); void testInoutFunction(const(char[]) function(const(char)[]) fn); void testInoutFunction(invariant(char[]) function(invariant(char)[]) fn); Adding an additional parameter doesn't change a bit (function shouldn't change its behaviour drastically given additional parameter): inout(char)[] testInoutFunction(inout(char)[] a, inout(char[]) function(inout(char)[]) fn) { testInoutFunction(fn); // call zero-arg version // do something with a return a[1..$]; } So a rule here is that inout() constancy expansion doesn't take place for delegate and function pointers. ------- In this case, inout() constancy expantion does take place on delegates and function pointers. Let's see if it is useful: char[] foo1(char[] a); invariant(char)[] foo2(invariant(char)[] a); void testInoutFunction(inout(char[]) function(inout(char)[]) fn); testInoutFunction(&foo1); // assume, we wanna call like this testInoutFunction(&foo2); // and like this Given these uses cases, what assumptions can testInoutFunction make and actions take? void testInoutFunction(inout(char[]) function(inout(char)[]) fn) { char[] a = "hello".dup; a = fn(a); // invalid, violates foo2 typechecking invariant(char)[] b = "hello"; b = fn(b); // invalid, violates foo1 typechecking const(char)[] c = "hello"; c = fn(c); // ok } See, testInoutFunction's actually can't do much. In fact it is no different from void testInoutFunction(const(char)[] function(char(char)[]) fn); So the second use case is in fact incorrect and should not be allowed. Back to the original example:inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter... and inout(B), inout(A) there have that same constancy? Or is their constancy unrelated? --bbinout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn);Type of return value here depends on type of first argument (c), type of fn doesn't vary, it is always the same.
Oct 15 2008
"Bill Baxter" wroteOn Wed, Oct 15, 2008 at 2:31 PM, Denis Koroskin <2korden gmail.com> wrote:inout is a type modifier. At the time you call foo, inout(B) and inout(A) are not resolved to anything yet. But when you call fn, that is when the compiler determines what inout should resolve to. Like any signature, it is telling you what the type of the parameters will be during the fn function. However, the special inout modifier tells the compiler it is safe to cast back to the original parameter constancy (or in the case of mutilple parameters, the greatest common constancy). -SteveOn Wed, 15 Oct 2008 05:30:52 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:.. and inout(B), inout(A) there have that same constancy? Or is their constancy unrelated?Denis Koroskin wrote:We are here to discuss it. I made many suggestions, but I don't know whether they fine, too complex or just lame untill someone comments it (interesting enough, some reply without reading). What's wrong with typeof(this) (I just generalized your clone() idea combined with implicit upcasting suggested by Steven)? The "I don't like it" comment would be useful, too. What's wrong with inout/whatever? So far you brought just one 'problematic' example: inout(C) foo(inout(B) function(inout(A)) fn); I believe this is as meaningless as inout(A) foo(); // what does it return? A, const(A) or invariant(A)? and thus should be statically disallowed. I mean, inout(return) doesn't have any sense unless a function accepts some inout(parameter): inout(B) foo(inout(A) a); // ok // your example with an added inout(in) parameter. Now it is fine inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter.On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:If you can find a solution that is simple and general enough, my hat is off to you. AndreiSimen Kjaeraas wrote:IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 15 2008
Denis Koroskin wrote:On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas <simen.kjaras gmail.com> wrote:Er.... isn't that the point. If I pass two mutable arguments to min(), I want a mutable back. If I pass two invariant (immutable, hopefully!) arguments to min() I want an invariant back. Otherwise, I want a const back. That's why this syntax is needed at all.On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:YesSteven Schveighoffer wrote:I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?"Andrei Alexandrescu" wroteAccept and return any subtype of Base. AndreiSteven Schveighoffer wrote:In this last example, what does the inout mean at the front?"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);Also, this means a maximum of one inout argument type per function, right?No, there were many example of multiple (and even none) inout function arguments.As for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough.My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.
Oct 14 2008
On Wed, 15 Oct 2008 00:59:44 +0400, Robert Fraser <fraserofthenight gmail.com> wrote:Denis Koroskin wrote:Yes, it's all about this. My point is that 'inout' keyword is not very descriptive in this situation. The behaviour, however, is fine.[snip] My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.Er.... isn't that the point. If I pass two mutable arguments to min(), I want a mutable back. If I pass two invariant (immutable, hopefully!) arguments to min() I want an invariant back. Otherwise, I want a const back. That's why this syntax is needed at all.
Oct 14 2008
Andrei Alexandrescu wrote:Steven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
"KennyTM~" wroteAndrei Alexandrescu wrote:You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -SteveSteven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
On Tue, 14 Oct 2008 21:04:01 +0400, Steven Schveighoffer <schveiguy yahoo.com> wrote:"KennyTM~" wroteI think that basic idea is very good! (for some reason I was bound to constness issue and didn't think about the latter example).Andrei Alexandrescu wrote:Steven Schveighoffer wrote:"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) AndreiI have previously proposed the 'same' keyword: same(Base) foo(same(Base) bar) { bar.callSomeBaseMethod(); return bar; } foo(new Derived()); interface IClonable { same(this) clone(); } It is short and very descriptive. Frankly speaking, I don't see the difference between inout or some other keyword. You either ditch inout and introduce FOO or obsolete inout and reuse it. Both have similar effect: some code is broken and needs fixing (because inout doesn't work anymore), a new keyword is introduced to denote the feature. The only difference is a possible name collision that any new keyword might introduce. But it is nothing to worry about, because short and meaningful keywords are more important than some potentially broken (and easily fixable) code for the language in a long run.I'm afraid the meaning of inout here is very unclear without explanation.You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve
Oct 14 2008
Denis Koroskin wrote:On Tue, 14 Oct 2008 21:04:01 +0400, Steven Schveighoffer <schveiguy yahoo.com> wrote:“same” seems to be a common identifier though... “inout” already has an equivalent keyword. It is called “ref”."KennyTM~" wroteI think that basic idea is very good! (for some reason I was bound to constness issue and didn't think about the latter example).Andrei Alexandrescu wrote:Steven Schveighoffer wrote:"Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) AndreiI have previously proposed the 'same' keyword: same(Base) foo(same(Base) bar) { bar.callSomeBaseMethod(); return bar; } foo(new Derived()); interface IClonable { same(this) clone(); } It is short and very descriptive. Frankly speaking, I don't see the difference between inout or some other keyword. You either ditch inout and introduce FOO or obsolete inout and reuse it. Both have similar effect: some code is broken and needs fixing (because inout doesn't work anymore), a new keyword is introduced to denote the feature. The only difference is a possible name collision that any new keyword might introduce. But it is nothing to worry about, because short and meaningful keywords are more important than some potentially broken (and easily fixable) code for the language in a long run.I'm afraid the meaning of inout here is very unclear without explanation.You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve
Oct 14 2008
On Tue, 14 Oct 2008 22:16:33 +0400, KennyTM~ <kennytm gmail.com> wrote:“same” seems to be a common identifier though.. “inout” already has an equivalent keyword. It is called “ref”.Yeah, I know :) Thanks anyway. But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.
Oct 14 2008
"Denis Koroskin" wroteOn Tue, 14 Oct 2008 22:16:33 +0400, KennyTM~ <kennytm gmail.com> wrote:I'd say if we were to add a keyword, same would be on the list of choices, I can't think of a common usage for it. But you can't deny that in general using a commonly used symbol as a keyword might be a bad idea. For example 'get' or 'next'. So yes, when adding a keyword, one should be wary of how it affects commonly used keywords, and yes, 'same' is a good choice in that regard ;) For example, when porting Tango to D2, I had to change about 5 or so singleton members (and their uses) named 'shared' since it is now a keyword. -Steve"same" seems to be a common identifier though.. "inout" already has an equivalent keyword. It is called "ref".Yeah, I know :) Thanks anyway. But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.
Oct 14 2008
Denis Koroskin wrote:On Tue, 14 Oct 2008 22:16:33 +0400, KennyTM~ <kennytm gmail.com> wrote:Googled "filetype:cpp same" and got ≥1 obscure results: void series::init() { hide = false; fl_sr2 = same; fl_ga = same; fl_gb = same; // -snip- } (http://evgenii.rudnyi.ru/soft/varcomp/varcomp/sumsqrut.cpp) But no result so far in D. Probably a safe choice after all, but to me too un-keyword-like :p.“same” seems to be a common identifier though.. “inout” already has an equivalent keyword. It is called “ref”.Yeah, I know :) Thanks anyway. But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.
Oct 14 2008
Denis Koroskin Wrote:But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.google codesearch says that "same" is used in mozilla code and "inout" - in audacity code.
Oct 15 2008
Steven Schveighoffer wrote:"KennyTM~" wroteSorry, I can't think of an existing keyword that can clearly qualify the purpose in this syntax (“stuff(Type) f(stuff(Type) s)”) either. By the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's “typeof” syntax, I can at least guess what the function looks like (it returns the same type as “s”, OK); but with “inout” I just stopped with "What the heck is going on?!".Andrei Alexandrescu wrote:You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -SteveSteven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
"KennyTM~" wroteSteven Schveighoffer wrote:I meant a new keyword. There aren't many existing keywords that I'd want to double the meaning of for this (including typeof). inout is advantageous because its current meaning is exactly covered by another keyword."KennyTM~" wroteSorry, I can't think of an existing keyword that can clearly qualify the purpose in this syntax ("stuff(Type) f(stuff(Type) s)") either.Andrei Alexandrescu wrote:You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -SteveSteven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) AndreiBy the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's "typeof" syntax, I can at least guess what the function looks like (it returns the same type as "s", OK)Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); ... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]; but with "inout" I just stopped with "What the heck is going on?!".At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs. I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it? -Steve
Oct 14 2008
Steven Schveighoffer wrote:"KennyTM~" wroteYeah I know. It may better be called sth like typeof callsite(s) :)Steven Schveighoffer wrote:I meant a new keyword. There aren't many existing keywords that I'd want to double the meaning of for this (including typeof). inout is advantageous because its current meaning is exactly covered by another keyword."KennyTM~" wroteSorry, I can't think of an existing keyword that can clearly qualify the purpose in this syntax ("stuff(Type) f(stuff(Type) s)") either.Andrei Alexandrescu wrote:You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -SteveSteven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) AndreiBy the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's "typeof" syntax, I can at least guess what the function looks like (it returns the same type as "s", OK)Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); ... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]That could be an advantage. We've different viewpoints :); but with "inout" I just stopped with "What the heck is going on?!".At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs.I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it?I'd use eval() if I designed the language :p (Though this also confuses user as eval() is more used in runtime code evaluation)-Steve
Oct 14 2008
Steven Schveighoffer wrote:"KennyTM~" wrote[snip]FWIW I agree with KennyTM~ here--typeof(s) made sense to me, including its meaning that you describe above. This, even though it conflicts with the keyword's current meaning. I suppose it's because "typeof(s)" could stand for "some type of s", i.e. some child type of s. The thing that concerns me about using typeof(s) is your earlier comment:By the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's "typeof" syntax, I can at least guess what the function looks like (it returns the same type as "s", OK)Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); .... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]; but with "inout" I just stopped with "What the heck is going on?!".At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs. I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it? -SteveI understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[].-Dave
Oct 14 2008
On Tue, 14 Oct 2008 23:17:18 +0400, David Gileadi <foo bar.com> wrote:Steven Schveighoffer wrote:Using typeof, expression would get way too complex and involve some magic template trickery like PassQual!() to pass constancy qualifiers from one variables to another. Besides, typeof proved to be insufficient to solve the problem: typeof(t1) min(T)(T t1, typeof(t1) t2); T t1 = ...; auto t2 = ...; auto t3 = min(t1, t2); // what is the type of t3? Is it T per typeof(t1)? // Answer is - it depends.. on t2: T t1 = ...; const(T) t2 = ...; auto t3 = min(t1, t2); // typeof(t3) == const(T)"KennyTM~" wrote[snip]FWIW I agree with KennyTM~ here--typeof(s) made sense to me, including its meaning that you describe above. This, even though it conflicts with the keyword's current meaning. I suppose it's because "typeof(s)" could stand for "some type of s", i.e. some child type of s. The thing that concerns me about using typeof(s) is your earlier comment:By the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's "typeof" syntax, I can at least guess what the function looks like (it returns the same type as "s", OK)Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); .... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]; but with "inout" I just stopped with "What the heck is going on?!".At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs. I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it? -SteveI understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[].-Dave
Oct 14 2008
KennyTM~ wrote:Sorry, I can't think of an existing keyword that can clearly qualify the purpose in this syntax (“stuff(Type) f(stuff(Type) s)”) either. By the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's “typeof” syntax, I can at least guess what the function looks like (it returns the same type as “s”, OK); but with “inout” I just stopped with "What the heck is going on?!".I can't: typeof(s) foo(const(char)[] s) This looks like it's shorthand for: const(char)[] foo(const(char)[] s) And the examples with typeof(A), where A is a type, just didn't make any sense.
Oct 14 2008
KennyTM~ wrote:Andrei Alexandrescu wrote:I think more explanation is needed for the other case: inout(char)[] stripl(inout(char)[] s); What if I pass a const char[]? Is that going to work? In contrast, this is simple: inout stripl(inout const(char)[] s); Accept and return any subtype of const(char)[]. AndreiSteven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
"Andrei Alexandrescu" wroteKennyTM~ wrote:I like this a lot better. What about returning a member? i.e.: inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;} is that what you had in mind? this syntax is going to be used often, since it's what you would use for an accessor. So it should be simple to understand if possible. -SteveAndrei Alexandrescu wrote:I think more explanation is needed for the other case: inout(char)[] stripl(inout(char)[] s); What if I pass a const char[]? Is that going to work? In contrast, this is simple: inout stripl(inout const(char)[] s); Accept and return any subtype of const(char)[].Steven Schveighoffer wrote:I'm afraid the meaning of inout here is very unclear without explanation."Andrei Alexandrescu" wroteI agree. We need to look for a better notation.I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s);As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
Steven Schveighoffer wrote:What about returning a member? i.e.: inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}Yah there is a recurring problem - we need to find a notation that works nicely and expressively for member functions as well as free functions. For free functions an easy-to-explain-and-understand way is to have "inout" mark the incoming / outgoing TYPE entirely, not only its qualifier. Then: inout stripl(inout const(char)[] s); means: accept any subtype of const(char)[] and call that type "inout" in the return type. Then it's easy to access dependent stuff: typeof(inout.ptr) at(inout const(char)[] s); Notice that for one-parameter functions there's not even a need to specify the inout in the argument list because it's unambiguous: inout stripl(const(char)[] s); typeof(inout.ptr) at(const(char)[] s); When you get into member functions things aren't quite nice: class A { private int a; inout clone() inout; // ehm typeof(&inout.a) getPtrToA() inout; // ehm }is that what you had in mind? this syntax is going to be used often, since it's what you would use for an accessor. So it should be simple to understand if possible.Yah I agree. At this point I don't have any solid solution for notation... please continue rolling out ideas. Andrei
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:Damn, I think there is some confusion here, because we are trying to solve two related, but unequal problems. First is const equivariance (is that the right term?). I want to specify a function that treats an argument as const, but does not affect the const contract that the caller has with that argument (i.e. my original scoped const proposal). The second is type equivariance. I want to specify that I will treat an argument as a base type, but I will not change the derived type that the caller is using. These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type. So the first requirement (const equivariance) does not require that I return a derivative of the argument, it requires that I return an argument that is part of the input, but contains the same const contract as the passed in variable at the *call site*. It does not require that the return type is derived from the input. It is this first requirement that is the only requirement necessary for functions which return unrelated types from their arguments. i.e., for an accessor, which is not returning something of the same type as its argument, the only thing that is important to keep the same at the call site is the const modifiers. For example, you can return a base type that is automatically converted to a derived type, but that must be accomplished with an overridden function. That is already handled using covariance, and I really don't think there is a way to enforce this in the compiler. An example, a linked list. class Link { private Link next; Link getNext() { return next;} } class DerivedLink : Link { int someExtraData; } We currently solve this by adding a covariant getNext() to DerivedLink. But would it be enough to just change the getNext function in the base type to: inout getNext() { return next; } inout; I don't think it can be enforced. Because some other Link function can set next to another type of Link (possibly a base Link). It is up to the designer of DerivedLink to ensure that next always points to a descendent of DerivedLink. In this case, I think it's a requirement to use covariant functions. However, it *would* be advantageous to declare getNext as not altering the const contract of the caller. That is, it returns the same constancy that it is called with (in this example, inout implies const): inout(Link) getNext() { return next; } inout; For the second requirement, I can see value in things like call-chaining: class C { inout doSomething() inout { return this;} } class D : C { inout doSomethingElse() inout { return this;} } auto d = new D; d.doSomething().doSomethingElse(); However, the return value MUST be exactly the same value as the input. If it's only the same type, we lose all compiler enforcibility. So does it make sense to split these two requirements? I propose to use inout only to deal with const contracts, and use another syntax to signify which parameter will be returned: inout(X) foo(inout(Y) y); // X and Y are unrelated types a stab at 'returning this exact parameter' X foo(return X x); // The return value from foo will be x, so the compiler is free to upcast automatically. inout(X) foo(return inout(X) x); // combination, signifies that I will return x, and I promise not to modify it in the function. With this scheme, I think possibly inout might not be as clear... -SteveWhat about returning a member? i.e.: inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}Yah there is a recurring problem - we need to find a notation that works nicely and expressively for member functions as well as free functions. For free functions an easy-to-explain-and-understand way is to have "inout" mark the incoming / outgoing TYPE entirely, not only its qualifier. Then: inout stripl(inout const(char)[] s); means: accept any subtype of const(char)[] and call that type "inout" in the return type. Then it's easy to access dependent stuff: typeof(inout.ptr) at(inout const(char)[] s); Notice that for one-parameter functions there's not even a need to specify the inout in the argument list because it's unambiguous: inout stripl(const(char)[] s); typeof(inout.ptr) at(const(char)[] s); When you get into member functions things aren't quite nice: class A { private int a; inout clone() inout; // ehm typeof(&inout.a) getPtrToA() inout; // ehm }is that what you had in mind? this syntax is going to be used often, since it's what you would use for an accessor. So it should be simple to understand if possible.Yah I agree. At this point I don't have any solid solution for notation... please continue rolling out ideas.
Oct 14 2008
Steven Schveighoffer wrote:These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type.Base type and supertype are not to be confused. By base type I understand you mean you actually type class Super{} and class Sub:Super{}. The subtyping relation is more general and applies to any two types Super and Sub that satisfy a number of constraints. That const(T) is a supertype of both T and invariant(T) is essential and not happenstance. It enshrines the fact that you can always pass a T or an invariant(T) into a function. The fact that there is no difference in layout and that const is not assignable also means that arrays of const(T) are supertypes of both arrays of T and invariant(T), with important consequences. Andrei
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:But a type invariant(T) is not a subtype of const(U). How do you solve this problem: class U { private T field public T property() { return field;} public invariant(T) property() { return field; } invariant; public const(T) property() {return field; } const; } Because T is completely unrelated to U, so the inout contract only has to do with constancy, not the types. -SteveThese are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type.Base type and supertype are not to be confused. By base type I understand you mean you actually type class Super{} and class Sub:Super{}. The subtyping relation is more general and applies to any two types Super and Sub that satisfy a number of constraints. That const(T) is a supertype of both T and invariant(T) is essential and not happenstance. It enshrines the fact that you can always pass a T or an invariant(T) into a function. The fact that there is no difference in layout and that const is not assignable also means that arrays of const(T) are supertypes of both arrays of T and invariant(T), with important consequences.
Oct 14 2008
Steven Schveighoffer wrote:"Andrei Alexandrescu" wroteWell I agree. I was just pointing out a minute fact.Steven Schveighoffer wrote:But a type invariant(T) is not a subtype of const(U).These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type.Base type and supertype are not to be confused. By base type I understand you mean you actually type class Super{} and class Sub:Super{}. The subtyping relation is more general and applies to any two types Super and Sub that satisfy a number of constraints. That const(T) is a supertype of both T and invariant(T) is essential and not happenstance. It enshrines the fact that you can always pass a T or an invariant(T) into a function. The fact that there is no difference in layout and that const is not assignable also means that arrays of const(T) are supertypes of both arrays of T and invariant(T), with important consequences.How do you solve this problem: class U { private T field public T property() { return field;} public invariant(T) property() { return field; } invariant; public const(T) property() {return field; } const; } Because T is completely unrelated to U, so the inout contract only has to do with constancy, not the types.Yah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier. Andrei
Oct 14 2008
Andrei Alexandrescu wrote:Yah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier.FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.
Oct 14 2008
On Wed, 15 Oct 2008 01:18:40 +0400, Robert Fraser <fraserofthenight gmail.com> wrote:Andrei Alexandrescu wrote:People didn't complain that they were nude until someone discovered it :) I mean, human needs grow over time as they discover something useful.Yah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier.FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.
Oct 14 2008
On Wed, Oct 15, 2008 at 6:23 AM, Denis Koroskin <2korden gmail.com> wrote:On Wed, 15 Oct 2008 01:18:40 +0400, Robert Fraser <fraserofthenight gmail.com> wrote:Agreed. There are uses for equivariance if you have it. I think especially so when thinking about functional style programming where you are chaining many functions together. It's nice if the chain you create doesn't lose the input object's true most derived identity. You can fake it with templates if you really have to, but then if they are member functions they become non-virtual. --bbAndrei Alexandrescu wrote:People didn't complain that they were nude until someone discovered it :) I mean, human needs grow over time as they discover something useful.Yah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier.FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.
Oct 14 2008
Bill Baxter wrote:On Wed, Oct 15, 2008 at 6:23 AM, Denis Koroskin <2korden gmail.com> wrote:Yah. What's the name of the feature? I was told a couple of times; I always forget it. AndreiOn Wed, 15 Oct 2008 01:18:40 +0400, Robert Fraser <fraserofthenight gmail.com> wrote:Agreed. There are uses for equivariance if you have it. I think especially so when thinking about functional style programming where you are chaining many functions together. It's nice if the chain you create doesn't lose the input object's true most derived identity. You can fake it with templates if you really have to, but then if they are member functions they become non-virtual.Andrei Alexandrescu wrote:People didn't complain that they were nude until someone discovered it :) I mean, human needs grow over time as they discover something useful.Yah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier.FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.
Oct 14 2008
I'm looking at all these funky syntaxes with their many unusual implicit assumptions that they require a user to keep in mind, and thinking it would all be a lot simpler if you could just declare some template-like meta-variables in a preamble to the function. I also don't like the looks of using a symbol in the return type that doesn't get declared till you see the parameter list. So how about introducing a "typedef block" where you can introduce metatypes and give them constraints. For constness: typedef { Const : const } // in this case means any kind of const Const(int) someFunc(Const(int) z) { ... } For Objects: class BaseClass {} class DerivedClass : BaseClass {} typedef { DType : BaseClass } // DType is anything passing is(DType : BaseClass) DType someFunc(DType input) For both: typedef { DType : BaseClass; Const : const } Const(DType) someFunc(Const(DType) input) And with something like this you can also come up with some solution to the min problem typedef{ ConstA : const; ConstB : const; ConstC = maxConst!(ConstA,ConstB) } ConstC(Type) max(ConstA(Type), ConstB(Type)) maxConst would be some template-like thing func to take the "more const" of the two qualifiers. Not sure how you'd implement that. Lots o hole's there, but the basic idea I'm suggesting is to declare the types and qualifiers that can vary in some sort of a preamble block. To me this looks like it will be much more readable and easier to maintain than any of these funky syntaxes with lots of implicit rules to remember. --bb
Oct 14 2008
"Bill Baxter" wroteI'm looking at all these funky syntaxes with their many unusual implicit assumptions that they require a user to keep in mind, and thinking it would all be a lot simpler if you could just declare some template-like meta-variables in a preamble to the function. I also don't like the looks of using a symbol in the return type that doesn't get declared till you see the parameter list. So how about introducing a "typedef block" where you can introduce metatypes and give them constraints. For constness: typedef { Const : const } // in this case means any kind of const Const(int) someFunc(Const(int) z) { ... } For Objects: class BaseClass {} class DerivedClass : BaseClass {} typedef { DType : BaseClass } // DType is anything passing is(DType : BaseClass) DType someFunc(DType input) For both: typedef { DType : BaseClass; Const : const } Const(DType) someFunc(Const(DType) input) And with something like this you can also come up with some solution to the min problem typedef{ ConstA : const; ConstB : const; ConstC = maxConst!(ConstA,ConstB) } ConstC(Type) max(ConstA(Type), ConstB(Type)) maxConst would be some template-like thing func to take the "more const" of the two qualifiers. Not sure how you'd implement that. Lots o hole's there, but the basic idea I'm suggesting is to declare the types and qualifiers that can vary in some sort of a preamble block. To me this looks like it will be much more readable and easier to maintain than any of these funky syntaxes with lots of implicit rules to remember.I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax? To put the use cases in more english descriptions: 1. I want the constancy of my return to be dependent on the constancy of the argument at the call site. 2. I want the constancy of my return to be the greatest common constancy of multiple arguments at the call site. 3. The return type will be exactly the same value that I passed in to the function, so I can use it for call chaining. So it's OK to auto cast to the most derived type. 4. I want 1 and 3 Where greatest common constancy is defined as const if any arguments differ in constancy. If they don't differ it is defined as the constancy of the arguments. My biggest concern is 1 and 2, since otherwise for 1, we are left with 3 different versions of the same function, and for 2, we are left with 3^n different versions. 3 (and 4) are of mild concern, because you are only forced to redefine once per class, and it's not as common a problem, since not all classes require handling of call chaining, whereas all classes should be const-aware. I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword. and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function). -Steve
Oct 14 2008
Steven Schveighoffer wrote:I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword.Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall. Andrei
Oct 14 2008
On Wed, Oct 15, 2008 at 11:02 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Steven Schveighoffer wrote:Ew. Yeh, that is sticky. This will not be that common though. Maybe you could allow arguments/indexes on the inout for those cases? inout[0](X) fun(inout[0](Y) function(inout[1](Z)) gun);I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword.Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.So what's the use case you have in mind that that doesn't satisfy? --bband for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.
Oct 14 2008
Bill Baxter wrote:On Wed, Oct 15, 2008 at 11:02 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:If you'd ask for my opinion, I'd say the show of inout stops here.inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.Ew. Yeh, that is sticky. This will not be that common though. Maybe you could allow arguments/indexes on the inout for those cases? inout[0](X) fun(inout[0](Y) function(inout[1](Z)) gun);Too many. I mean most all. Essentially you can only implement identity and min/max :o). Think of stripl when you need to return a slice of the incoming argument. AndreiSo what's the use case you have in mind that that doesn't satisfy?and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.
Oct 14 2008
On Wed, Oct 15, 2008 at 11:20 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Bill Baxter wrote:But stripl just needs constness propagation, right? So it's handled by Steve's inout proposal. The return thing just handles propagation of base type. I assume he has some way to combine inout with return when you want to manipulate both constness and base type. Maybe I've missed something though... --bbOn Wed, Oct 15, 2008 at 11:02 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:If you'd ask for my opinion, I'd say the show of inout stops here.inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.Ew. Yeh, that is sticky. This will not be that common though. Maybe you could allow arguments/indexes on the inout for those cases? inout[0](X) fun(inout[0](Y) function(inout[1](Z)) gun);Too many. I mean most all. Essentially you can only implement identity and min/max :o). Think of stripl when you need to return a slice of the incoming argument.So what's the use case you have in mind that that doesn't satisfy?and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:That would be invalid. You need at least one inout argument that can be implicitly casted to const. As far as I know that can't be done with a function pointer. If you want to pass in a function pointer to a function which uses the inout constructs, then the inouts become literal inout. i.e.: inout(Y) foo(inout(Z) x) {...} inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} invariant(Z) z = cast(invariant) new Z; auto x = fun(z, &foo);// x is of type invariant(X) Maybe you had a different behavior in mind?I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword.Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.I agree that the usefulness is not huge. But there is no other compiler checkable way to guarantee that it can do the cast. If the function in question doesn't know about the derived type, how can it generate a new instance that can be casted to the derived type? It MUST return the exact argument. The only possible other use is for typedefs, but if you are using typedefs, there isn't a huge incentive if you want to use them exactly like the base type. And I don't want to define all functions in this style just in case someone has typedef'd the return type, and they want the compiler to auto-cast back to the typedef'd type. I'll note that I can live without case 3, as the usefulness is low. -Steveand for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.
Oct 14 2008
Steven Schveighoffer wrote:"Andrei Alexandrescu" wroteWell this: inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} could mean two things: 1. "Bind all inout occurrences to a qualifier and resolve the signature", or 2. "Whenever inout occurs in a function parameter, don't bind it." It's not that clear-cut which is the one to be chosen, and that will have to be a convention because the syntax is ambiguous. AndreiSteven Schveighoffer wrote:That would be invalid. You need at least one inout argument that can be implicitly casted to const. As far as I know that can't be done with a function pointer. If you want to pass in a function pointer to a function which uses the inout constructs, then the inouts become literal inout. i.e.: inout(Y) foo(inout(Z) x) {...} inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} invariant(Z) z = cast(invariant) new Z; auto x = fun(z, &foo);// x is of type invariant(X) Maybe you had a different behavior in mind?I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword.Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:OK, how about this: inout(Y) function(inout(Z)) f = &foo; auto x = fun(z, f); What is f? Is this not the same as passing &foo directly? -Steve"Andrei Alexandrescu" wroteWell this: inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} could mean two things: 1. "Bind all inout occurrences to a qualifier and resolve the signature", or 2. "Whenever inout occurs in a function parameter, don't bind it." It's not that clear-cut which is the one to be chosen, and that will have to be a convention because the syntax is ambiguous.Steven Schveighoffer wrote:That would be invalid. You need at least one inout argument that can be implicitly casted to const. As far as I know that can't be done with a function pointer. If you want to pass in a function pointer to a function which uses the inout constructs, then the inouts become literal inout. i.e.: inout(Y) foo(inout(Z) x) {...} inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} invariant(Z) z = cast(invariant) new Z; auto x = fun(z, &foo);// x is of type invariant(X) Maybe you had a different behavior in mind?I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword.Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.
Oct 14 2008
On Wed, Oct 15, 2008 at 10:13 AM, Steven Schveighoffer <schveiguy yahoo.com> wrote:"Bill Baxter" wroteThink of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration. And note that C++ gets along fine without any such assistance. It is far from "all your functions" that require such handling, that's why it doesn't kill C++ not to have it. For returning the input type, it has been commented "do we even really need this?" since most other popular languages don't have such a thing, and it is generally not a "buzz feature" you hear about much (unlike closures or lambdas, etc). So I don't think its use will be that common, thus a little more verbose syntax will not be an undue burden. Indeed, precisely because it will not be used frequently, it *should* have a more verbose, more explicit syntax. So that on those occasions when it is used it will A) not sneak past the casual observer's notice and B) have at least some chance that the meaning of the code can be guessed without having to look it up.I'm looking at all these funky syntaxes with their many unusual implicit assumptions that they require a user to keep in mind, and thinking it would all be a lot simpler if you could just declare some template-like meta-variables in a preamble to the function. I also don't like the looks of using a symbol in the return type that doesn't get declared till you see the parameter list. So how about introducing a "typedef block" where you can introduce metatypes and give them constraints. For constness: typedef { Const : const } // in this case means any kind of const Const(int) someFunc(Const(int) z) { ... } For Objects: class BaseClass {} class DerivedClass : BaseClass {} typedef { DType : BaseClass } // DType is anything passing is(DType : BaseClass) DType someFunc(DType input) For both: typedef { DType : BaseClass; Const : const } Const(DType) someFunc(Const(DType) input) And with something like this you can also come up with some solution to the min problem typedef{ ConstA : const; ConstB : const; ConstC = maxConst!(ConstA,ConstB) } ConstC(Type) max(ConstA(Type), ConstB(Type)) maxConst would be some template-like thing func to take the "more const" of the two qualifiers. Not sure how you'd implement that. Lots o hole's there, but the basic idea I'm suggesting is to declare the types and qualifiers that can vary in some sort of a preamble block. To me this looks like it will be much more readable and easier to maintain than any of these funky syntaxes with lots of implicit rules to remember.I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax?To put the use cases in more english descriptions: 1. I want the constancy of my return to be dependent on the constancy of the argument at the call site. 2. I want the constancy of my return to be the greatest common constancy of multiple arguments at the call site. 3. The return type will be exactly the same value that I passed in to the function, so I can use it for call chaining. So it's OK to auto cast to the most derived type. 4. I want 1 and 3 Where greatest common constancy is defined as const if any arguments differ in constancy. If they don't differ it is defined as the constancy of the arguments. My biggest concern is 1 and 2, since otherwise for 1, we are left with 3 different versions of the same function, and for 2, we are left with 3^n different versions. 3 (and 4) are of mild concern, because you are only forced to redefine once per class, and it's not as common a problem, since not all classes require handling of call chaining, whereas all classes should be const-aware. I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword. and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).That does sound reasonable for the use cases you've outlined. I hadn't realized you were making "inout" mean "maximal const". I can't think of any use case for non-maximal const of the top of my head so aside from the "inout" being not a great word, I think I could live with this ... unless someone discovers another use case. If inout is useless now then we could retire it and add a new "anyconst" keyword. Or "vconst" for "variable/various/varying/virtual const" vconst(X) f(vconst(Y) y, vconst(Z) z) Or stick some syntax on it. Like "const~", the squiggle indicating "similar to". Or "const?". const?(X) f(const?(Y) y, const?(Z) z); --- Here's something that comes up -- iterators in C++ usually end up needing to come in const and non-const flavors. The "head" of both is mutable, but the "tail" is const on the const flavor. How do you write this pair of functions as one?: const(X) getValue(const_iterator iter); X getValue(iterator iter); I guess I can answer that myself. If you wrote an apropriate IterType!(T) template then maybe with your syntax you could say: inout(X) getValue(IterType!(inout(X)) iter); and with my suggestion it would be something like typedef {Const : const } Const(X) getValue(IterType!(Const(X)) iter); Ok, so I guess that's handled OK. --bb
Oct 14 2008
"Bill Baxter"wroteOn Wed, Oct 15, 2008 at 10:13 AM, Steven SchveighofferAn example class with 10 accessors (forget about the implementations): Your way: class Owner { typedef { Const : const } Const(A) a() Const {...} typedef { Const : const } Const(B) b() Const {...} typedef { Const : const } Const(C) c() Const {...} typedef { Const : const } Const(D) d() Const {...} typedef { Const : const } Const(E) e() Const {...} typedef { Const : const } Const(F) f() Const {...} typedef { Const : const } Const(G) g() Const {...} typedef { Const : const } Const(H) h() Const {...} typedef { Const : const } Const(I) i() Const {...} // written by another author who prefers a different identifier for Const typedef { Konst : const } Konst(J) j() Konst {...} } My way: class Owner { inout(A) a() inout {...} inout(B) b() inout{...} inout(C) c() inout{...} inout(D) d() inout{...} inout(E) e() inout{...} inout(F) f() inout{...} inout(G) g() inout{...} inout(H) h() inout{...} inout(I) i() inout{...} inout(J) j() inout{...} } It might be just me, but my way seems cleaner and easier to implement.I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax?Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration.And note that C++ gets along fine without any such assistance. It is far from "all your functions" that require such handling, that's why it doesn't kill C++ not to have it.But C++ doesn't have transitive const. It is my experience that most C++ classes are not implemented as const-aware, or are but implemented const aware incorrectly. Including classes I wrote. I think Walter is right when he says that C++ const is a mess.For returning the input type, it has been commented "do we even really need this?" since most other popular languages don't have such a thing, and it is generally not a "buzz feature" you hear about much (unlike closures or lambdas, etc). So I don't think its use will be that common, thus a little more verbose syntax will not be an undue burden. Indeed, precisely because it will not be used frequently, it *should* have a more verbose, more explicit syntax. So that on those occasions when it is used it will A) not sneak past the casual observer's notice and B) have at least some chance that the meaning of the code can be guessed without having to look it up.It is not something I really am pushing. My main concern is the const variance. Something like this makes const so much more appealing (remember, no other languages have tried transitive const). However, with an additional feature that requires all derived classes to re-implement a function, this can be more useful than just 'return the input value'. Think clone(). As far as requiring a clumsy syntax, especially where changing style is possible but has no real affect on the result (i.e. using Konst instead of Const), I don't see that as a benefit.I'm not super fond of inout, but it allows not adding a new keyword. Some view that as an essential part of this.To put the use cases in more english descriptions: 1. I want the constancy of my return to be dependent on the constancy of the argument at the call site. 2. I want the constancy of my return to be the greatest common constancy of multiple arguments at the call site. 3. The return type will be exactly the same value that I passed in to the function, so I can use it for call chaining. So it's OK to auto cast to the most derived type. 4. I want 1 and 3 Where greatest common constancy is defined as const if any arguments differ in constancy. If they don't differ it is defined as the constancy of the arguments. My biggest concern is 1 and 2, since otherwise for 1, we are left with 3 different versions of the same function, and for 2, we are left with 3^n different versions. 3 (and 4) are of mild concern, because you are only forced to redefine once per class, and it's not as common a problem, since not all classes require handling of call chaining, whereas all classes should be const-aware. I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword. and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function).That does sound reasonable for the use cases you've outlined. I hadn't realized you were making "inout" mean "maximal const". I can't think of any use case for non-maximal const of the top of my head so aside from the "inout" being not a great word, I think I could live with this ... unless someone discovers another use case.If inout is useless now then we could retire it and add a new "anyconst" keyword. Or "vconst" for "variable/various/varying/virtual const" vconst(X) f(vconst(Y) y, vconst(Z) z) Or stick some syntax on it. Like "const~", the squiggle indicating "similar to". Or "const?". const?(X) f(const?(Y) y, const?(Z) z);All these sound like valid alternatives, I'd be fine with any of them.Here's something that comes up -- iterators in C++ usually end up needing to come in const and non-const flavors. The "head" of both is mutable, but the "tail" is const on the const flavor. How do you write this pair of functions as one?:You can't cast an iterator to 'head const' (another nagging problem, but a separate one), so the only viable option is templates. -Steve
Oct 15 2008
Steven Schveighoffer wrote:The const? has been on the table. It is a fave of mine because it offers a consistent solution for a related issue - detecting lvalueness: void foo(ref? Widget w); This means foo should bind to a ref Widget if you pass it an lvalue, and to a Widget if offered an rvalue. There is precedent on using "x?" to mean an optional x in regexes, so I wouldn't be surprised if many figured what const? does when first seeing it. One issue have with const? is that it binds the behavior to const, thereby eliminating the chance of making things more general. Also the const? will have to do something NOT suggested by the notation, namely pass the invariant, if present, along. Anyhow, I wanted to share one more thought. On the face of it, as someone already said, we DO have a solution to avoid code duplication: templates. Maybe a fertile direction to take would be to use regular template syntax and just let the compiler figure out that it can actually define only one function instead of three. What I'm saying is that we're really trying to beat the compiler in the head until it understands that: S stripl(S s) if (is(S : const(char)[]) { stmts } really means to us: char[] stripl(char[] s) { stmts } const(char)[] stripl(const(char)[] s) { stmts } invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { stmts } What if, instead of finding yet another notation for that simple fact, we actually used the "right" notation (it is right because it's already there!) and figure out whether the compiler can understand it? I was about to post when I realized that that idea won't work prettily at all with member functions... sigh :o/ Andreiconst?(X) f(const?(Y) y, const?(Z) z);All these sound like valid alternatives, I'd be fine with any of them.
Oct 15 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:I've convinced myself at this point that the const piece has to be separate from the 'derived type' piece. Both can be required separately, i.e. I want to allow variations to the constancy of a specific type, but not to derivative types. Therefore, it makes the most sense to me to make each feature use a distinct syntax. So I have no problem with this notation for the const variant. As far as invariant, I think it is well established that people don't gripe or sing the praises of the const-invariant system, they generally refer to it as the const system. I think it's already a defacto standard that const in D includes invariant. They are clearly related.The const? has been on the table. It is a fave of mine because it offers a consistent solution for a related issue - detecting lvalueness: void foo(ref? Widget w); This means foo should bind to a ref Widget if you pass it an lvalue, and to a Widget if offered an rvalue. There is precedent on using "x?" to mean an optional x in regexes, so I wouldn't be surprised if many figured what const? does when first seeing it. One issue have with const? is that it binds the behavior to const, thereby eliminating the chance of making things more general. Also the const? will have to do something NOT suggested by the notation, namely pass the invariant, if present, along.const?(X) f(const?(Y) y, const?(Z) z);All these sound like valid alternatives, I'd be fine with any of them.Anyhow, I wanted to share one more thought. On the face of it, as someone already said, we DO have a solution to avoid code duplication: templates. Maybe a fertile direction to take would be to use regular template syntax and just let the compiler figure out that it can actually define only one function instead of three. What I'm saying is that we're really trying to beat the compiler in the head until it understands that: S stripl(S s) if (is(S : const(char)[]) { stmts } really means to us: char[] stripl(char[] s) { stmts } const(char)[] stripl(const(char)[] s) { stmts } invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { stmts } What if, instead of finding yet another notation for that simple fact, we actually used the "right" notation (it is right because it's already there!) and figure out whether the compiler can understand it? I was about to post when I realized that that idea won't work prettily at all with member functions... sigh :o/Yeah, that would be tough for functions you want to make virtual. I think it needs to be required to be a single function implementation, not left up to the compiler. -Steve
Oct 15 2008
Andrei Alexandrescu Wrote:One issue have with const? is that it binds the behavior to const, thereby eliminating the chance of making things more general. Also the const? will have to do something NOT suggested by the notation, namely pass the invariant, if present, along.invariant is just a flavor of const and we're not restricted to regexp notation.What I'm saying is that we're really trying to beat the compiler in the head until it understands that: S stripl(S s) if (is(S : const(char)[]) { stmts }try to template this const?(X) fun(const?(Y) y); One big difference between normal and templated functions is you can provide only signature for normal function. Is it safe to typecheck/instantiate templated function without body?
Oct 16 2008
ore-sama Wrote:Andrei Alexandrescu Wrote:just a thought: class A{} class B{ A a; } DeriveConst!(I,A) fun(I)(I b) { return b.a; } unittest { B m; fun(m); const B c; fun(c); invariant B i; invariant A a=fun(i); } template DeriveConst(A,B) { static if(is(A==invariant))alias invariant(B) DeriveConst; else static if(is(A==const))alias const(B) DeriveConst; else alias B DeriveConst; }S stripl(S s) if (is(S : const(char)[]) { stmts }try to template this const?(X) fun(const?(Y) y);
Oct 21 2008
ore-sama wrote:ore-sama Wrote:Yah that was discussed under the name of PassQual. One problem is defining a palatable syntax for member functions. AndreiAndrei Alexandrescu Wrote:just a thought: class A{} class B{ A a; } DeriveConst!(I,A) fun(I)(I b) { return b.a; } unittest { B m; fun(m); const B c; fun(c); invariant B i; invariant A a=fun(i); } template DeriveConst(A,B) { static if(is(A==invariant))alias invariant(B) DeriveConst; else static if(is(A==const))alias const(B) DeriveConst; else alias B DeriveConst; }S stripl(S s) if (is(S : const(char)[]) { stmts }try to template this const?(X) fun(const?(Y) y);
Oct 21 2008
Andrei Alexandrescu wrote:Anyhow, I wanted to share one more thought. On the face of it, as someone already said, we DO have a solution to avoid code duplication: templates. Maybe a fertile direction to take would be to use regular template syntax and just let the compiler figure out that it can actually define only one function instead of three.here's a stab at a syntax similar to templates: T func(x : const, y : const)(X x, Y y, const Z z); in the above the return type T constancy is dependent on the constancy of x and y. z is a "regular" const. the idea is that X's *constancy* (not the entire type!) is subtype of const. here's a method: class Klass { T met(this: const, y : const)(X x, Y y, const Z z); } generic min function would look like: T min(T, x : const, y : const) (T x, T y) { ...} what do you think?
Oct 16 2008
Andrei Alexandrescu wrote:What I'm saying is that we're really trying to beat the compiler in the head until it understands that: S stripl(S s) if (is(S : const(char)[]) { stmts } really means to us: char[] stripl(char[] s) { stmts } const(char)[] stripl(const(char)[] s) { stmts } invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { stmts } What if, instead of finding yet another notation for that simple fact, we actually used the "right" notation (it is right because it's already there!) and figure out whether the compiler can understand it? I was about to post when I realized that that idea won't work prettily at all with member functions... sigh :o/ AndreiWouldn't work prettily with member functions because one can't parameterize on the actual 'this' argument, right? -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
Bruno Medeiros wrote:Andrei Alexandrescu wrote:Yah, one can't do that unless more notation is added... AndreiWhat I'm saying is that we're really trying to beat the compiler in the head until it understands that: S stripl(S s) if (is(S : const(char)[]) { stmts } really means to us: char[] stripl(char[] s) { stmts } const(char)[] stripl(const(char)[] s) { stmts } invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { stmts } What if, instead of finding yet another notation for that simple fact, we actually used the "right" notation (it is right because it's already there!) and figure out whether the compiler can understand it? I was about to post when I realized that that idea won't work prettily at all with member functions... sigh :o/ AndreiWouldn't work prettily with member functions because one can't parameterize on the actual 'this' argument, right?
Oct 18 2008
On Thu, Oct 16, 2008 at 12:46 AM, Steven Schveighoffer <schveiguy yahoo.com> wrote:"Bill Baxter"wroteGood example. Definitely seems easier to implement. And agreed that for common cases it has less clutter. I could suggest making some class-scoped version of the typedef{Const : const} thing, but at that point it would just become a different name for your "inout", and would have the disadvantage of different people giving different names to something that will be used in just about every class. Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)On Wed, Oct 15, 2008 at 10:13 AM, Steven SchveighofferAn example class with 10 accessors (forget about the implementations): Your way: class Owner { typedef { Const : const } Const(A) a() Const {...} [...] // written by another author who prefers a different identifier for Const typedef { Konst : const } Konst(J) j() Konst {...} } My way: class Owner { inout(A) a() inout {...} [...] } It might be just me, but my way seems cleaner and easier to implement.I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax?Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration.I don't understand this comment. Why would you need to cast iterators to "head const"? I think head const is not supported by the current const design at all -- via casting or otherwise. --bbHere's something that comes up -- iterators in C++ usually end up needing to come in const and non-const flavors. The "head" of both is mutable, but the "tail" is const on the const flavor. How do you write this pair of functions as one?:You can't cast an iterator to 'head const' (another nagging problem, but a separate one), so the only viable option is templates.
Oct 15 2008
"Bill Baxter" wroteOn Thu, Oct 16, 2008 at 12:46 AM, Steven Schveighoffer <schveiguy yahoo.com> wrote:lol! or should I say lol? I'm pretty sure this isn't taken: const!(x) and his emacs module could translate it to: const <<x>> (Yes, I have no idea how to type those weird characters ;)"Bill Baxter"wroteGood example. Definitely seems easier to implement. And agreed that for common cases it has less clutter. I could suggest making some class-scoped version of the typedef{Const : const} thing, but at that point it would just become a different name for your "inout", and would have the disadvantage of different people giving different names to something that will be used in just about every class. Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)On Wed, Oct 15, 2008 at 10:13 AM, Steven SchveighofferAn example class with 10 accessors (forget about the implementations): Your way: class Owner { typedef { Const : const } Const(A) a() Const {...} [...] // written by another author who prefers a different identifier for Const typedef { Konst : const } Konst(J) j() Konst {...} } My way: class Owner { inout(A) a() inout {...} [...] } It might be just me, but my way seems cleaner and easier to implement.I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax?Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration.Damn! I always do that! swap head const with tail const, sorry ;) -SteveI don't understand this comment. Why would you need to cast iterators to "head const"? I think head const is not supported by the current const design at all -- via casting or otherwise.Here's something that comes up -- iterators in C++ usually end up needing to come in const and non-const flavors. The "head" of both is mutable, but the "tail" is const on the const flavor. How do you write this pair of functions as one?:You can't cast an iterator to 'head const' (another nagging problem, but a separate one), so the only viable option is templates.
Oct 15 2008
Bill Baxter wrote:Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei
Oct 15 2008
Andrei Alexandrescu wrote:Bill Baxter wrote:Huh? But class Owner { const // without the “?” { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } only apply const on the function, but not their return type (i.e. they become A a() const { ... } // etc. but not const(A) a() const { ... } // etc. )Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei
Oct 15 2008
KennyTM~ wrote:Andrei Alexandrescu wrote:Yah, but it does not make sense to not apply const? to the return value. AndreiBill Baxter wrote:Huh? But class Owner { const // without the “?” { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } only apply const on the function, but not their return type (i.e. they become A a() const { ... } // etc. but not const(A) a() const { ... } // etc. )Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei
Oct 15 2008
"Andrei Alexandrescu" wroteKennyTM~ wrote:compromise:Andrei Alexandrescu wrote:Yah, but it does not make sense to not apply const? to the return value.Bill Baxter wrote:Huh? But class Owner { const // without the "?" { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } only apply const on the function, but not their return type (i.e. they become A a() const { ... } // etc. but not const(A) a() const { ... } // etc. )Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. AndreiAlso still not terrible. Whatever we come up with should be consistent with the other type modifiers. Otherwise, you get into sticky situations like this: const? { A a(X x) } Should this translate to: const?(A) a(const?(X) x) const? Sidenote: seeing that const? at the end really looks weird. Not sure I like this so much any more. I think I'd rather see a new keyword... -Steveclass Owner { const? { const?(A) a() {...} const?(B) b() {...} const?(C) c() {...} const?(D) d() {...} const?(E) e() {...} const?(F) f() {...} const?(G) g() {...} const?(H) h() {...} const?(I) i() {...} const?(J) j() {...} } }
Oct 15 2008
On Thu, Oct 16, 2008 at 4:10 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Bill Baxter wrote:I was thinking about that too. But I'm not a big fan of blocks that change the meaning of declarations in a non-local way. It looks quite readable in toy examples, but after you insert all the bodies and documentation comments for everything in the block, it becomes very easy to lose sight of the tag way back at the beginning of the block. --bbHmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad.
Oct 15 2008
Bill Baxter wrote:On Thu, Oct 16, 2008 at 4:10 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I disagree. A block is a block is a block. Indentation shows it's not non-local. I understand how you could formulate an argument against labels. They do change meaning in a non-obvious way. Block indentation makes all the difference. I'm in fact surprised to hear that from a pythonboi :o). AndreiBill Baxter wrote:I was thinking about that too. But I'm not a big fan of blocks that change the meaning of declarations in a non-local way. It looks quite readable in toy examples, but after you insert all the bodies and documentation comments for everything in the block, it becomes very easy to lose sight of the tag way back at the beginning of the block.Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad.
Oct 15 2008
On Thu, Oct 16, 2008 at 4:39 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Bill Baxter wrote:If the top of the block is off the screen it's not necessarily obvious that it's a block. And even if you do realize it's a block, you don't know what special attributes are in play without scrolling back up to see what it says. That's why I don't like em. Not sure what Python has to do with it. I think a method signature in Python can pretty much always be read and understood without having to look at some tag that exists further up the screen. Closest thing I can think of in Python is the decorators, but I think those always have to appear right before the method signature that they modify. --bbOn Thu, Oct 16, 2008 at 4:10 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I disagree. A block is a block is a block. Indentation shows it's not non-local. I understand how you could formulate an argument against labels. They do change meaning in a non-obvious way. Block indentation makes all the difference. I'm in fact surprised to hear that from a pythonboi :o).Bill Baxter wrote:I was thinking about that too. But I'm not a big fan of blocks that change the meaning of declarations in a non-local way. It looks quite readable in toy examples, but after you insert all the bodies and documentation comments for everything in the block, it becomes very easy to lose sight of the tag way back at the beginning of the block.Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad.
Oct 15 2008
Bill Baxter wrote:If the top of the block is off the screen it's not necessarily obvious that it's a block.By the same argument it's not obvious whether you're in a class, function, template etc. definition. All of them do change the context of what's going on. Andrei
Oct 15 2008
On Thu, Oct 16, 2008 at 5:57 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Bill Baxter wrote:The fact that I have to keep one thing in mind does not make an excuse for creating several other things I also have to keep in mind. I've heard the human mind can keep about 7 or 8 things active at once. It's a limited number. So the more info you can keep local the better. Same reason we don't declare all our variables at the tops of functions any more. Declaration at the point of use makes the type more readily available at the point in the code where you need to know it. Anyway, I've got no problem with collecting things in attribute blocks like that, as long as the blocks stay less than roughly a page in length. Similar to the commonly use rule of thumb for function length. --bbIf the top of the block is off the screen it's not necessarily obvious that it's a block.By the same argument it's not obvious whether you're in a class, function, template etc. definition. All of them do change the context of what's going on.
Oct 15 2008
On Wed, Oct 15, 2008 at 9:27 AM, Bill Baxter <wbaxter gmail.com> wrote:I'm looking at all these funky syntaxes with their many unusual implicit assumptions that they require a user to keep in mind, and thinking it would all be a lot simpler if you could just declare some template-like meta-variables in a preamble to the function. I also don't like the looks of using a symbol in the return type that doesn't get declared till you see the parameter list. So how about introducing a "typedef block" where you can introduce metatypes and give them constraints. For constness: typedef { Const : const } // in this case means any kind of const Const(int) someFunc(Const(int) z) { ... } For Objects: class BaseClass {} class DerivedClass : BaseClass {} typedef { DType : BaseClass } // DType is anything passing is(DType : BaseClass) DType someFunc(DType input) For both: typedef { DType : BaseClass; Const : const } Const(DType) someFunc(Const(DType) input) And with something like this you can also come up with some solution to the min problem typedef{ ConstA : const; ConstB : const; ConstC = maxConst!(ConstA,ConstB) } ConstC(Type) max(ConstA(Type), ConstB(Type)) maxConst would be some template-like thing func to take the "more const" of the two qualifiers. Not sure how you'd implement that. Lots o hole's there, but the basic idea I'm suggesting is to declare the types and qualifiers that can vary in some sort of a preamble block. To me this looks like it will be much more readable and easier to maintain than any of these funky syntaxes with lots of implicit rules to remember.Actually, let me take a step back from any concrete syntax. What I think would be an easy-to-read and easy-to-use system would look like this in pseudocode: let "Const" be any kind of constness let "BType" be any kind of object derived from BaseObject in the following declaration: Const(BType) theFunction(Const(BType) x, string y) { ... } In other words, *first* explicitly give names to the things that will vary, *then* use those names in the method signature. I think this will be much easier to use and more flexible than making up a bunch of rules with the aim of implying the same things via subtle variations of the basic declaration syntax. --bb
Oct 14 2008
Robert Fraser wrote:Andrei Alexandrescu wrote:Well Scala, Eiffel and probably other languages have support for at least equivariance of the current object. I always forget the name of that feature. AndreiYah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier.FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.
Oct 14 2008
On Wed, 15 Oct 2008 00:31:15 +0400, Steven Schveighoffer <schveiguy yahoo.com> wrote:"Andrei Alexandrescu" wroteI think you brought very interesting topic with a second example! However, I believe it may be solved easily. Let's start with your example: class A { final A foo() { // do something return this; } } class B : A { } It is obvious that for any type B which is a subclass of A it is safe to do the following: B b = ...; b = cast(B)b.foo(); // this is guarantied to be safe This, however, is only true if the foo() method is final (I talk about current D for now). So you say, why not to allow this without casting by making some rules so that compiler could ensure code correctness. I believe this is possible and the method signature could be as Andrei proposed: class A { final typeof(this) foo() { // do something return this; } } This is consistent with the behaviour: return type is always covariant with the this type at callsite. Once again, this operation is guarantied to be safe if method returns 'this' and is final. But we want to take it one step forward! For example, we may want to make it virtual, not final. In this case we can lift the restriction to return this as well, subject to the following constrains: 1) Method may be made final if and only if it returns this. 2) If base class method is not final then the derived class have to override the method. 3) Class may return some other pointer, not only this, but it should statically check that the pointer type is covariant with this type. Note1: Class could also be allowed to not override non-final method of the base class if it returns this (it is safe to do so). But this greatly increases checking rules complexity. In some cases body might not be available so compiler can't whether we return this and therefore are forced to override it anyway. Note2: method that returns null might be also allowed to be final. Let's put some more examples: module example1; class A { final typeof(this) foo() { return this; } } class B // ok { void bar(); } (new B()).foo().bar(); module example2; class A { final typeof(this) foo1(); // body is in some other file, guarantied to return this typeof(this) foo2(); } class B { typeof(this) foo2() { super.foo2(); return this; } void bar() {} } (new B()).foo2().foo1().bar(); module example3; class Link { typeof(this) next() { return _next; // ok, typeof(next) == typeof(this). Note that we can't make this method final } private Link _next; } class DerivedLink1 : Link { int someExtraData; // fails to compile: class doesn't override typeof(this) next(); } class DerivedLink2 : Link { int someExtraData; typeof(this) next() { return cast(DerivedLink)super.next(); // ok, might return null } } auto data = (new DerivedLink2()).next().next().someExtraData; module example4; class A { typeof(this) clone() { return new A(this); // note that we can't make this method final } } class B1 : A { // fails to compile, *have to* override clone() } class B2 : A { /*final*/ typeof(this) clone() { return null; // it's ok. But shall we allow final methods to return null? } } class B3 : A { typeof(this) clone() { return new A(); // error, is (A : typeof(this)) is false } } class B4 : A { typeof(this) clone() { return new B4(); } void bar() {} } (new B4()).clone().clone().bar(); I think this may work.Steven Schveighoffer wrote:Damn, I think there is some confusion here, because we are trying to solve two related, but unequal problems. First is const equivariance (is that the right term?). I want to specify a function that treats an argument as const, but does not affect the const contract that the caller has with that argument (i.e. my original scoped const proposal). The second is type equivariance. I want to specify that I will treat an argument as a base type, but I will not change the derived type that the caller is using. These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type. So the first requirement (const equivariance) does not require that I return a derivative of the argument, it requires that I return an argument that is part of the input, but contains the same const contract as the passed in variable at the *call site*. It does not require that the return type is derived from the input. It is this first requirement that is the only requirement necessary for functions which return unrelated types from their arguments. i.e., for an accessor, which is not returning something of the same type as its argument, the only thing that is important to keep the same at the call site is the const modifiers. For example, you can return a base type that is automatically converted to a derived type, but that must be accomplished with an overridden function. That is already handled using covariance, and I really don't think there is a way to enforce this in the compiler. An example, a linked list. class Link { private Link next; Link getNext() { return next;} } class DerivedLink : Link { int someExtraData; } We currently solve this by adding a covariant getNext() to DerivedLink. But would it be enough to just change the getNext function in the base type to: inout getNext() { return next; } inout; I don't think it can be enforced. Because some other Link function can set next to another type of Link (possibly a base Link). It is up to the designer of DerivedLink to ensure that next always points to a descendent of DerivedLink. In this case, I think it's a requirement to use covariant functions. However, it *would* be advantageous to declare getNext as not altering the const contract of the caller. That is, it returns the same constancy that it is called with (in this example, inout implies const): inout(Link) getNext() { return next; } inout; For the second requirement, I can see value in things like call-chaining: class C { inout doSomething() inout { return this;} } class D : C { inout doSomethingElse() inout { return this;} } auto d = new D; d.doSomething().doSomethingElse(); However, the return value MUST be exactly the same value as the input. If it's only the same type, we lose all compiler enforcibility. So does it make sense to split these two requirements? I propose to use inout only to deal with const contracts, and use another syntax to signify which parameter will be returned: inout(X) foo(inout(Y) y); // X and Y are unrelated types a stab at 'returning this exact parameter' X foo(return X x); // The return value from foo will be x, so the compiler is free to upcast automatically. inout(X) foo(return inout(X) x); // combination, signifies that I will return x, and I promise not to modify it in the function. With this scheme, I think possibly inout might not be as clear... -SteveWhat about returning a member? i.e.: inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}Yah there is a recurring problem - we need to find a notation that works nicely and expressively for member functions as well as free functions. For free functions an easy-to-explain-and-understand way is to have "inout" mark the incoming / outgoing TYPE entirely, not only its qualifier. Then: inout stripl(inout const(char)[] s); means: accept any subtype of const(char)[] and call that type "inout" in the return type. Then it's easy to access dependent stuff: typeof(inout.ptr) at(inout const(char)[] s); Notice that for one-parameter functions there's not even a need to specify the inout in the argument list because it's unambiguous: inout stripl(const(char)[] s); typeof(inout.ptr) at(const(char)[] s); When you get into member functions things aren't quite nice: class A { private int a; inout clone() inout; // ehm typeof(&inout.a) getPtrToA() inout; // ehm }is that what you had in mind? this syntax is going to be used often, since it's what you would use for an accessor. So it should be simple to understand if possible.Yah I agree. At this point I don't have any solid solution for notation... please continue rolling out ideas.
Oct 14 2008
Tue, 14 Oct 2008 16:31:15 -0400, Steven Schveighoffer wrote:Damn, I think there is some confusion here, because we are trying to solve two related, but unequal problems. First is const equivariance (is that the right term?). I want to specify a function that treats an argument as const, but does not affect the const contract that the caller has with that argument (i.e. my original scoped const proposal). The second is type equivariance. I want to specify that I will treat an argument as a base type, but I will not change the derived type that the caller is using. These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type.This is a very good point. I think these cases are different, too. And I think there is another confusion which leads to obscure syntaxes. Any function argument has two types: the call-site and the function-site, the latter is made from the former by an implicit cast. You must specify function-site type for the function body to compile properly, and you want to know a call-site type for equi-trickery. But that's not all. An equi-function should have two return types: one function-site for the body to compile properly, and one call-site for the type system to work there. Let's experiment with an explicit syntax. I'll define a calltype() built-in function similar to typeof() but yielding a call-site type in a function definition context. And I'll use cast() to specify the call- site return type of a function. cast(calltype(s)) const(char)[] stripl(const(char)[] s); Here cast() explicitly defines this function as a variant function. class A { cast(calltype(this)) A clone() { return new A; } } Now a verbose one: cast(PassQual!(calltype(a), calltype(b))(char)[]) const(char)[] choose(const(char)[] a, const(char)[] b, bool which); Now that I look at this, all the magic is concentrated within the cast() block and is actually quite close to Bill Baxter's typedef {} proposal. And it seems like the call-site types are only needed there, and they're the only types needed. So typeof() can be used there with modified semantics. I even think that inout() can be used instead of cast() with pretty much the same effect on readability. So a simple covariant function would look like this: inout(typeof(s)) const(char)[] foo(const(char)[] s) { return s[1..$]; } which is translated into: T foo(T)(T s) { return cast(T) _foo_impl(s); } const(char)[] _foo_impl(const(char)[] s) { return s[1..$]; } Though I'd prefer cast() or var() or covar() as a covariant modifier.
Oct 15 2008
"Sergey Gromov" wroteTue, 14 Oct 2008 16:31:15 -0400, Steven Schveighoffer wrote:Auto-deducing the return type is only half the problem. During the function, the compiler must make certain guarantees about s, and about the return type. For example, something cannot be implicitly cast to the type of s, since its true type is not known at compile-time, only it's base type. And since the return type must match the constancy of the input, the return type must be restricted to only the type of s. Since you can't implicitly cast to s, you must return s or a subset of s. But the return type doesn't necessarily have to be related to s, it could be just of the same constancy. Someone asked, what if you are returning a private field (i.e. through an accessor). Would you use typeof(this.privateField) in the signature? Doesn't this expose some private field name? What if there was no field that existed for the type you wanted to return? You'd have to do kludgy stuff like typeof(T.init), but then how do you flag the argument as being const-variant? I think we need something to flag the argument as const-variant, and something to flag the return value as const-variant. It's clearer that the argument is being affected, and it's clearer what you want to return. To me: inout(char)[] stripl(inout(char)[] s); is much clearer than typeof(s) stripl(const(char)[] s); I can look at each part of the signature, and know exactly what is going on. With the second syntax, everything has references to other pieces, so I have to look back and forth to see what is happening. If s is buried in a long list of arguments, it's even less clear. Note also that the typeof(T) syntax does not help functions with multiple arguments without template kludginess (and I can't see how the compiler enforces the guarantees, given a custom template), whereas using a builtin type modifier handles it perfectly. At least for const-variance, a type modifier is ideal. Some of the other ideas might benefit from a typeof (arg) syntax. -SteveDamn, I think there is some confusion here, because we are trying to solve two related, but unequal problems. First is const equivariance (is that the right term?). I want to specify a function that treats an argument as const, but does not affect the const contract that the caller has with that argument (i.e. my original scoped const proposal). The second is type equivariance. I want to specify that I will treat an argument as a base type, but I will not change the derived type that the caller is using. These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type.This is a very good point. I think these cases are different, too. And I think there is another confusion which leads to obscure syntaxes. Any function argument has two types: the call-site and the function-site, the latter is made from the former by an implicit cast. You must specify function-site type for the function body to compile properly, and you want to know a call-site type for equi-trickery. But that's not all. An equi-function should have two return types: one function-site for the body to compile properly, and one call-site for the type system to work there. Let's experiment with an explicit syntax. I'll define a calltype() built-in function similar to typeof() but yielding a call-site type in a function definition context. And I'll use cast() to specify the call- site return type of a function. cast(calltype(s)) const(char)[] stripl(const(char)[] s); Here cast() explicitly defines this function as a variant function. class A { cast(calltype(this)) A clone() { return new A; } } Now a verbose one: cast(PassQual!(calltype(a), calltype(b))(char)[]) const(char)[] choose(const(char)[] a, const(char)[] b, bool which); Now that I look at this, all the magic is concentrated within the cast() block and is actually quite close to Bill Baxter's typedef {} proposal. And it seems like the call-site types are only needed there, and they're the only types needed. So typeof() can be used there with modified semantics. I even think that inout() can be used instead of cast() with pretty much the same effect on readability. So a simple covariant function would look like this: inout(typeof(s)) const(char)[] foo(const(char)[] s) { return s[1..$]; } which is translated into: T foo(T)(T s) { return cast(T) _foo_impl(s); } const(char)[] _foo_impl(const(char)[] s) { return s[1..$]; } Though I'd prefer cast() or var() or covar() as a covariant modifier.
Oct 15 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:One thing that should be mentioned, my original post implied that the 'base type' is implied to be const (since originally, this is the only problem I was trying to solve). So in my example: inout(char)[] stripl(inout(char)[] s); s is implied to be const(char) during the function. With your interpretation of inout meaning 'anything that is derived from', then I suppose the correct syntax should be: inout(const(char))[] stripl(inout(const(char))[] s); With your special trick of implying the return type looking like this: inout[] stripl(inout(const(char))[] s); Not as appealing, but it still works. -SteveOne other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword.Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o)
Oct 14 2008
Andrei Alexandrescu wrote:What do you think? I'm almost afraid to post this.I vote "no" on the whole feature. As far is I can tell, it doesn't add any new capabilities to the language, except for working around difficult corner cases in the cost system, and I think it's a mistake to add features whose sole purpose is to workaround the deficiencies in other features. If the const system has holes in it, I vote for patching those holes by redesigning the const system, not by adding a workaround. Or maybe I'm mistaken. Are there any compelling examples which don't revolve around constancy? --benji
Oct 14 2008
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Many functions return one of their parameters regardless of the way it was qualified. char[] stripl(char[] s); const(char)[] stripl(const(char)[] s); invariant(char)[] stripl(invariant(char)[] s); Stripl is not a particularly good example because it needs to work on wchar and dchar too, but let's ignore that aspect for now. There's been several proposals in this group on tackling that problem. In unrelated proposals and discussions, people mentioned the need for functions that return the exact type of this: class A { A clone(); } class B : A { B clone(); } How can we declare A.clone such that all of its derived classes have it return their own type? It took me a while to realize they are really very related. This is easy to figure out if you think that invariant(char)[] and char[] are subtypes of const(char)[]! I discussed with Walter a variant that implements equivariant functions without actually adding an explicit feature to the language. Consider: typeof(s) stripl(const(char)[] s); This signature states that it returns the same type as an argument. I propose that that pattern means stripl can accept _any_ subtype of const(char)[] and return that exact type. Inside the function, however, the type of s is the type declared, thus restricting its use. I need to convince myself that function bodies of this type can be reliably typechecked, but first I wanted to run it by everyone to get a feel of it. Equivariant functions are not (necessarily) templates and can be used as virtual functions. Only one body is generated for one equivariant function, unless other template mechanisms are in vigor. Here are some examples: a) Simple equivariance typeof(s) stripl(const(char)[] s); b) Parameterized equivariance typeof(s) stripl(S)(S s) if (isSomeString!S); c) Equivariance of field: typeof(s.ptr) getpointer(const(char)[] s); d) Equivariance inside a class/struct declaration: class S { typeof(this) clone(); typeof(this.field) getfield(); int field; } What do you think? I'm almost afraid to post this. AndreiNow that I thought about it a little more (please, see and comment my post about typeof(this) nearby), I agree that the issues are related. However, the best solution could be a combination of both. For example, I agree that interface IClonable should be as follows: interface IClonable { typeof(this) clone() const; } But how about this scenario: class Bar : IClonable { this(const(Bar) proto) { ... } typeof(this) clone() const { return new Bar(this); } } const(Bar) bar = ...; auto barClone = bar.clone(); // typeof(barClone) is const(Bar)! Knowing you, I expect you will suggest to change the IClonable interface as follows: interface IClonable { DropConst!(typeof(this)) clone() const; } Although I think that templates would be very useful here, I think in many cases they make things more complex than it could be. I believe things could get better if we would decouple the issue into two orthogonal concepts: a) typeof(this) is a type of 'foo' in a foo.bar() expression without any qualifiers applied. b) 'same' gives the qualifier which is common across all arguments 'same' is applied to (for example, void foo(same(A) a, same(B) b, same(C) c);, same is none, const or invariant). Besides, I would be great if typeof(this) would be interchangeble with the actual type of this with little or no loss of meaning. For example, with typeof(this) returning class type with all the qualifiers and no 'same', we would write: class Foo { typeof(this._bar) bar() const { return _bar; } private Bar _bar; } This have two negative sides: 1) we expose our internal structure 2) we need some template trickery if there is no member of type Bar inside Foo: interface Foo { PassQual!(typeof(this), Bar) bar(); } 3) the typeof(this._bar) is not interchangeble with Bar (qualifiers are lost) and function doesn't work anymore (can't cast const(Bar) to Bar implicitly) I also believe it is important to do the trick *without* templates, because there are people that don't like templates or don't know about them (e.g. starters). With 'same' returning qualifiers of the arguments (hidden ones included), we could write as follows: interface Foo { same(Bar) bar() same(this); } interface IClonable { typeof(this) clone(); // nothing else is needed! typeof(this) doesn't contain qualifiers. } note that changing typeof(this) -> IClonable works well here, too. Another example. We will use both of the concepts in pair now: class A { same(typeof(this)) foo() same(this) { // do something return this; } } class B : A { same(typeof(this)) foo() same(this) { super.foo(); bar(); return this; } void bar() {} } Little is changed if we replace typeof(this) with A and B in first and second classes respectively. A short summary: - typeof(s) is not very suitable for deducing qualifiers. It is quite ugly and not intuitive. See Christopher's comment: On Wed, 15 Oct 2008 04:09:59 +0400, Christopher Wright <dhasenan gmail.com> wrote:typeof(s) foo(const(char)[] s) This looks like it's shorthand for: const(char)[] foo(const(char)[] s) And the examples with typeof(A), where A is a type, just didn't make any sense.- There are cases where typeof(s) can't be applied at all (e.g. multiple argument parameters). inout/same is essential here - typeof(s) requires templates in way too many cases. same could be used to pass qualifiers without templates - dividing the concepts into two orthogonal may make it both easier to implement and easier to understand - this will also not overload the meaning of typeof() with too much different concepts This is just an idea, what do you think?
Oct 14 2008
"Denis Koroskin" wroteNow that I thought about it a little more (please, see and comment my post about typeof(this) nearby), I agree that the issues are related. However, the best solution could be a combination of both. For example, I agree that interface IClonable should be as follows: interface IClonable { typeof(this) clone() const; }No. IClonable should be: IClonable clone() const; or if you prefer: Object clone() const; It can't be anything else, because IClonable cannot define how many levels deep it goes. For example, if I have: class A : IClonable { typeof(this) clone() const { return new A();} } class B : A { } B b = new B; B x = b.clone(); Oops, b.clone() really only returns A, so this should fail. The real signature in A should be: class A : IClonable { A clone() const { return new A(); } } IClonable is a perfect candidate for covariant functions, which is already defined. -Steve
Oct 14 2008
Steven Schveighoffer wrote:class A : IClonable { typeof(this) clone() const { return new A();} } class B : A { } B b = new B; B x = b.clone(); Oops, b.clone() really only returns A, so this should fail. The real signature in A should be:No, b.clone() definitely returns an instance of B. It's purely coincidental (from the caller's perspective) that B's implementation is identical to that of A. Though I always thought the correct implementation of ICloneable was like this: interface ICloneable(T) { T clone(); } A : ICloneable!(A) { public A clone() { ... } } I really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system. Any other use case where equivariance might apply (other than the const stuff) seems to already have a simple, straightforward solution using either templates, interfaces, or plain old overloads. --benji
Oct 14 2008
Benji Smith wrote:I really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system.Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome. Andrei
Oct 14 2008
Andrei Alexandrescu wrote:Benji Smith wrote:Fair enough. Here are the reasons I describe the const system as "complex": * Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... } const(char)[] join(const(char)[] str1, const(char)[] str1) { ... } * Nestable const type constructors require me to keep a more detailed mental model of the type system. Am I using a "const(char)[]" or a "const(char[])"? * Remembering the difference between "const" and "invariant" requires more mental energy than using a system (D1) where such distinctions don't exist. * The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment. * The transitivity of const qualifiers requires me to consider not just the object I'm passing around, but the references to other objects contained within the const-qualified object. * Both "const" and "invariant" can apply to value-type objects. But "const" doesn't make any sense in that context, so in my mind I have to use the "invariant" concept instead whenever applying the concept to a primitive value. * The equivariance proposal, at least in some of its forms, requires me to imagine the existence of implicit typedefs at the function-call site, and to know that the declared type in my method signature might not be the actual type of the arguments received by the function. The "holes" I described in the const system are the symptoms being addressed by the equivariance proposal. Without equivariance, the const system sometimes requires verbatim duplication of code within function bodies whose only difference is their signature. Without the const system, the equivariance proposal wouldn't be necessary. And the reason I describe the const system as "confusing"? Because it confuses me. I understand some of the allure of having a robust, provably correct const system. Automatic memoization would be cool. Automatic parallel execution would be nice. And, in theory, it'd be nice to avoid errors that occur when a piece of code modifies a piece of data that it shouldn't. But in my experience (using languages that lack any sort of const system, except for enum-style declarations), it just doesn't matter much. When I need memoization or parallel execution, I write the code explicitly. And when I need an immutable object, I design its interface to make external mutation impossible (see also: java.lang.String). It's indisputable that the D2 const system is measurably more complex than the D1 const system. For me, the cost of that complexity significantly outweighs the benefit. --benjiI really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system.Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.
Oct 14 2008
Benji Smith wrote:Andrei Alexandrescu wrote:In my ideal language, I'd like a const system where instead of the instances of types (primitive or not) being qualified, the types themselves were. So a "const class" or "const struct" would mean that every instance of that type is constant after construction. So you'd have: --- class MutableType { int x; this(int _x) { this.x = _x; } } const class ConstType { int x; this(int _x) { this.x = _x; } } MutableType m = new MutableType(5); ConstType c = new ConstType(5); m.x = 10; // OK c.x = 10; // ERROR --- All classes that extend a const class would also need to be const. As for primitives, they would either be manifest-constant or mutable like in D1. This covers most common use cases very well (except for constant arrays... hmmmm....) and is easy for the user to understand. Plus, a "const class" under this system is the same as an "invariant" under the current system, so no synchronization required. Making invariant classes (classes that are final and where all members are final) are a common design pattern in Java. But I'm dreaming here since Walter seems dead-set on this system.Benji Smith wrote:Fair enough. Here are the reasons I describe the const system as "complex": * Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... } const(char)[] join(const(char)[] str1, const(char)[] str1) { ... } * Nestable const type constructors require me to keep a more detailed mental model of the type system. Am I using a "const(char)[]" or a "const(char[])"? * Remembering the difference between "const" and "invariant" requires more mental energy than using a system (D1) where such distinctions don't exist. * The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment. * The transitivity of const qualifiers requires me to consider not just the object I'm passing around, but the references to other objects contained within the const-qualified object. * Both "const" and "invariant" can apply to value-type objects. But "const" doesn't make any sense in that context, so in my mind I have to use the "invariant" concept instead whenever applying the concept to a primitive value. * The equivariance proposal, at least in some of its forms, requires me to imagine the existence of implicit typedefs at the function-call site, and to know that the declared type in my method signature might not be the actual type of the arguments received by the function. The "holes" I described in the const system are the symptoms being addressed by the equivariance proposal. Without equivariance, the const system sometimes requires verbatim duplication of code within function bodies whose only difference is their signature. Without the const system, the equivariance proposal wouldn't be necessary. And the reason I describe the const system as "confusing"? Because it confuses me. I understand some of the allure of having a robust, provably correct const system. Automatic memoization would be cool. Automatic parallel execution would be nice. And, in theory, it'd be nice to avoid errors that occur when a piece of code modifies a piece of data that it shouldn't. But in my experience (using languages that lack any sort of const system, except for enum-style declarations), it just doesn't matter much. When I need memoization or parallel execution, I write the code explicitly. And when I need an immutable object, I design its interface to make external mutation impossible (see also: java.lang.String). It's indisputable that the D2 const system is measurably more complex than the D1 const system. For me, the cost of that complexity significantly outweighs the benefit. --benjiI really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system.Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.
Oct 15 2008
Robert Fraser <fraserofthenight gmail.com> wrote:In my ideal language, I'd like a const system where instead of the instances of types (primitive or not) being qualified, the types themselves were. So a "const class" or "const struct" would mean that every instance of that type is constant after construction. So you'd have: --- class MutableType { int x; this(int _x) { this.x = _x; } } const class ConstType { int x; this(int _x) { this.x = _x; } } MutableType m = new MutableType(5); ConstType c = new ConstType(5); m.x = 10; // OK c.x = 10; // ERROR --- All classes that extend a const class would also need to be const. As for primitives, they would either be manifest-constant or mutable like in D1. This covers most common use cases very well (except for constant arrays... hmmmm....) and is easy for the user to understand. Plus, a "const class" under this system is the same as an "invariant" under the current system, so no synchronization required. Making invariant classes (classes that are final and where all members are final) are a common design pattern in Java. But I'm dreaming here since Walter seems dead-set on this system.Like in current d2, you mean? const class foo { } class bar : foo { } void main() { auto a = new foo(); auto b = new bar(); foo f = new foo(); writefln(typeof(a).stringof); // prints const(foo) writefln(typeof(b).stringof); // prints const(bar) writefln(typeof(f).stringof); // prints const(foo) } -- Simen
Oct 15 2008
Simen Kjaeraas wrote:Robert Fraser <fraserofthenight gmail.com> wrote:Well, yes and no. That is typeof(a) would be just "foo", there would be no such thing as a const(foo). Instances of foo just couldn't be changed after construction, and the compiler could apply invariant optimizations to it as usual. That is, there would be no user-visible const except for the ability to declare constant classes/structs.In my ideal language, I'd like a const system where instead of the instances of types (primitive or not) being qualified, the types themselves were. So a "const class" or "const struct" would mean that every instance of that type is constant after construction. So you'd have: --- class MutableType { int x; this(int _x) { this.x = _x; } } const class ConstType { int x; this(int _x) { this.x = _x; } } MutableType m = new MutableType(5); ConstType c = new ConstType(5); m.x = 10; // OK c.x = 10; // ERROR --- All classes that extend a const class would also need to be const. As for primitives, they would either be manifest-constant or mutable like in D1. This covers most common use cases very well (except for constant arrays... hmmmm....) and is easy for the user to understand. Plus, a "const class" under this system is the same as an "invariant" under the current system, so no synchronization required. Making invariant classes (classes that are final and where all members are final) are a common design pattern in Java. But I'm dreaming here since Walter seems dead-set on this system.Like in current d2, you mean? const class foo { } class bar : foo { } void main() { auto a = new foo(); auto b = new bar(); foo f = new foo(); writefln(typeof(a).stringof); // prints const(foo) writefln(typeof(b).stringof); // prints const(bar) writefln(typeof(f).stringof); // prints const(foo) }
Oct 16 2008
On Tue, 14 Oct 2008 22:51:46 -0400, Benji Smith <dlanguage benjismith.net> wrote:Andrei Alexandrescu wrote:But how would you know that the parameters are not modified? This could lead to unnecessary dups in the client code. Personally, I think it is easier to read a function signature, however complex, than to read the function body. The syntax is very important to the understanding of the concept, so far I like the idea of 'const?' and the typedef{} idea, but maybe reusing the keyword 'auto' could also work? class S { auto(this) clone(); auto(this.field) getfield(); int field; } GideBenji Smith wrote:Fair enough. Here are the reasons I describe the const system as "complex": * Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... }I really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system.Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.
Oct 15 2008
On Tue, 14 Oct 2008 22:51:46 -0400, Benji Smith <dlanguage benjismith.net> wrote: [snip]It's indisputable that the D2 const system is measurably more complex than the D1 const system. For me, the cost of that complexity significantly outweighs the benefit. --benjiThe following bug illustrates the issues users can have with D1's const system. http://d.puremagic.com/issues/show_bug.cgi?id=2418 Gide
Oct 15 2008
Benji Smith wrote:Andrei Alexandrescu wrote:I see where you're coming from. If your opinion is that the const system does not bring enough benefits to justify its learning, you are of course entitled to it. But talking about complexity implies that there would be simpler ways to achieve its charter that we missed, and talking about holes that we made mistakes of principle that go beyond bugs in the compiler or incompleteness of implementation.Benji Smith wrote:Fair enough. Here are the reasons I describe the const system as "complex":I really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system.Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.* Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... } const(char)[] join(const(char)[] str1, const(char)[] str1) { ... }Complexity should be judged in proportion to the goal achieved. Of course an AM radio will be less complex than an AM/FM radio; that does not reveal an intrinsic problem with the latter. And the more complex signature tells you that join will not change its arguments. As Gide mentioned, one source of bugs in a D program is that attention must be paid to duplicating aliased slices properly. That wouldn't be bad if such mistakes were easily detected. Unfortunately, forgetting to call .dup causes long-distance coupling that make changing one piece of data in one place influence the change in an unrelated part, just because they share some history.* Nestable const type constructors require me to keep a more detailed mental model of the type system. Am I using a "const(char)[]" or a "const(char[])"?You don't need to keep a more detailed mental model of the type system - whatever that is :o). On the contrary, const allows you to free your mind of many details about the data flow in your application. If you make a mistake regarding const(char)[] vs. const(char[]), the compiler will tell you about it, not one night of debugging.* Remembering the difference between "const" and "invariant" requires more mental energy than using a system (D1) where such distinctions don't exist.I think this reflects a misunderstanding of what constness is supposed to do for you. It's exactly the contrary. Invariant reduces the amount of mental energy you need to expend. I think you owe it to yourself to use D2. You will see that using invariant strings is a huge improvement and it alone is worth introducing constness. Literally all bugs caused by undue aliasing disappear if you use string. You will see that the net result is that your programs are easier to write and understand, not harder.* The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment.You may want to reframe this as an opportunity to improve your understanding of subtyping. I recommend a classic paper by Cardelli and Wegner "On understanding Types, Data Abstraction, and Polymorphism" (http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf). It's a classic. Reading through section 1.5 does not require much background and is very rewarding. That's not to say using const and invariant requires formal background. Most people would just use them for their benefits and have them just work. But if you want to lift the hood and see exactly why they work, you have to get your hands greasy a bit.* The transitivity of const qualifiers requires me to consider not just the object I'm passing around, but the references to other objects contained within the const-qualified object.This is backwards, too. If const didn't exist or weren't transitive, you'd be required to keep in mind for mutation and aliasing bugs not just the object you're passing around, but the references to other objects.* Both "const" and "invariant" can apply to value-type objects. But "const" doesn't make any sense in that context, so in my mind I have to use the "invariant" concept instead whenever applying the concept to a primitive value.Of course it does make sense. The address of a const int has a different type than the address of a mutable int. Again the pattern is that you have less, not more to keep in mind. The type "keeps it in its mind" for you.* The equivariance proposal, at least in some of its forms, requires me to imagine the existence of implicit typedefs at the function-call site, and to know that the declared type in my method signature might not be the actual type of the arguments received by the function.Not at all. On this group many people have high competency and willing to discuss the most minute implementation details of a language feature, and my understanding is that you also engage in such discussions. On occasion, therefore, there will be discussion on exactly how sausages are being made. Do not confuse those discussions with things that complicate the experience of eating sausages. Much of the problems caused by const equivariance is just that Walter is leery of implementing aliases bound to qualifiers. If he would, there would be no problem to talk about. The issues with const equivariance, in that case, would be exactly the same as with supporting equivariance for any types in general.The "holes" I described in the const system are the symptoms being addressed by the equivariance proposal.You did not describe any hole above. In fact, on first reading, at this point I was expecting you'd start describing the actual holes that I know about - e.g. the impossibility of creating invariant objects without a cast. (That will be fixed.)Without equivariance, the const system sometimes requires verbatim duplication of code within function bodies whose only difference is their signature. Without the const system, the equivariance proposal wouldn't be necessary.No. Without equivariance, you'd be required to duplicate bodies of functions that need it, such as clone. It is true that const makes that need more frequent, but that does not reveal a problem intrinsic to it or a mistake in defining it.And the reason I describe the const system as "confusing"? Because it confuses me.This is a fair point. One problem is that right now no good description of const exists. Worse, trying to infer it from discussions on this group is bound to scare one away because here mostly problems are discussed.I understand some of the allure of having a robust, provably correct const system. Automatic memoization would be cool. Automatic parallel execution would be nice. And, in theory, it'd be nice to avoid errors that occur when a piece of code modifies a piece of data that it shouldn't. But in my experience (using languages that lack any sort of const system, except for enum-style declarations), it just doesn't matter much. When I need memoization or parallel execution, I write the code explicitly. And when I need an immutable object, I design its interface to make external mutation impossible (see also: java.lang.String).There's been a pretty incredible resurgence in interest in functional programming languages. At ACCU 2008 (www.accu.org), previously a "no-quarters" hardcore C++ conference, who do you think gave keynotes and talks? Simon Peyton-Jones, the inventor of Haskell, and Joe Armstrong, inventor of Erlang. To better understand the size of that change, you must browse through previous years' conference programs. Only 2-3 years ago that very concept would have been unbelievable. ACCU stands for Association of C and C++ Users! Use of Haskell and Erlang is increasing exponentially. Even mainstream companies started using them. Others (big, big companies that shall not be named) are designing their own languages. And guess what - they _all_ have a notion of immutability. What do functional languages have? Do you think people like them for the monads? It's the immutability. They've been embracing and dealing with it for years. And that gives them a ready answer to today's 2000-lbs pink elephant in the room - the manycores. Having made a resolute step towards classic, deadlock-oriented programming, Java is now in the unpleasant position of having invested it money in the wrong stock, just like it did with UTF-16. So when people think manycores, they look away from Java.It's indisputable that the D2 const system is measurably more complex than the D1 const system. For me, the cost of that complexity significantly outweighs the benefit.It is fair to hold such a belief. I think it would be helpful to reevaluate it in wake of the above. Andrei
Oct 15 2008
Hey, Andrei! First of all, just let me say how much I appreciate your detailed responses. I hope you don't ever take my feedback as a personal criticism or as being harshly negative. The only reason I've continued to engage is these kinds of debates for the last five years or so is because D interests me very much, and I'd like to see it meet its fullest potential. I know you have the same end-goals in mind, though we sometimes disagree about the most effective route from point A to point B. Andrei Alexandrescu wrote:I see where you're coming from. If your opinion is that the const system does not bring enough benefits to justify its learning, you are of course entitled to it. But talking about complexity implies that there would be simpler ways to achieve its charter that we missed, and talking about holes that we made mistakes of principle that go beyond bugs in the compiler or incompleteness of implementation.Yeah, I see where you're coming from too. I think you guys have done an admirable job thinking through the corner cases and figuring out ways to solve many of the sticky design issues that have arisen. On the other hand, there was a const proposal...maybe six months or a year ago...where all function parameters would be considered immutable by default, unless annotated as mutable. I thought that idea had a lot of merit. My first impression was that it'd provide more reliable guarantees, since it would be impossible to accidentally create a mutable param by forgetting to declare its constness. It also seemed like it would have resulted in more compact method signatures (since const arguments are generally more common than mutable arguments), and less overall risk of aliasing. For me, one of the main perks of the other proposal was less "const" litter all over the code, and yet with stronger const safety. Also, a function promising not to modify its arguments seems more like an attribute of the function, not of the arguments themselves. But I digress...The "mental model" is my cute little informal way of referring to the body of knowledge necessary to operate some contraption (like a can opener or a compiler). In order to make the gizmo do what I want it to do, I have to form a (perhaps simplified) mental picture of the cause-and-effect chain that connects the inputs with the outputs. Driving a car with a manual transmission requires a more detailed mental model than driving an automatic. Maybe the manual transmission offers other advantages, but ease-of-use is not one of them.* Nestable const type constructors require me to keep a more detailed mental model of the type system. Am I using a "const(char)[]" or a "const(char[])"?You don't need to keep a more detailed mental model of the type system - whatever that is :o). On the contrary, const allows you to free your mind of many details about the data flow in your application. If you make a mistake regarding const(char)[] vs. const(char[]), the compiler will tell you about it, not one night of debugging.I actually plan on giving D2 a spin as soon as a compatible version of Tango is available. And I actually totally agree with you about invariant strings. The strings, without the languages having a const system like D. In those languages, immutability is achieved by convention. It's a tradeoff, sure. Especially when implementing a new class, you have to think carefully about when to make defensive copies in getters and setters. And external consumers of your classes, who might not know where the defensive copies occur, might create their own defensive copies before calling one of your API methods. In that respect, a const system is alluring because it eliminates the need for those kinds of considerations. But the const system just smells bad to me. I've tried to enumerate my reasons, and maybe switching to D2 will change my mind. But right now, I'm calling it as I see it. You asked for feedback, and you got it!! I'll make an analogy. I also find the concept of range-bound numeric types very appealing. It'd be nice to declare that a function takes an argument whose value is a "double greater than zero but less than or equal to one". A lot of my statistical code would benefit from that kind of type declaration, and I'd be able to get rid of a few lines of argument-validity-checking code from the preamble of every function implementation. While we're at it, I can also think of examples where I might like to say that a function accepts a "feature vector whose magnitude is exactly equal to 1.0". Input validation is one of those nasty cross-cutting concerns, and it'd be nice to have a general-purpose, language-level solution to the problem. It doesn't take too much imagination to sketch out a design for extending the type system to allow for those kinds of value constraints. With such a type calculus in place, it would probably be possible to implement a general purpose solver that would enable a whole host of interesting optimizations. If I'm not mistaken, that's the basic idea of many functional languages... That by moving computation into the type system, the algebraic rules governing those types can be solved by applying well-known type transformations, often in parallel. Nevertheless, I think range-bound types would be a mistake to implement in D. I can't completely articulate why I feel that way, but part of it has to do with the increasing complexity of function prototype declarations. And, it's also partly because validation can already be accomplished in an "in" contract. To me, the const system has the same basic characteristics: it's a major language feature, with lots of corner cases, to solve a problem that I either don't have or have already found a reasonable (if not ideal) solution.* Remembering the difference between "const" and "invariant" requires more mental energy than using a system (D1) where such distinctions don't exist.I think this reflects a misunderstanding of what constness is supposed to do for you. It's exactly the contrary. Invariant reduces the amount of mental energy you need to expend. I think you owe it to yourself to use D2. You will see that using invariant strings is a huge improvement and it alone is worth introducing constness. Literally all bugs caused by undue aliasing disappear if you use string. You will see that the net result is that your programs are easier to write and understand, not harder.I just read section 1.5 and don't find anything outlandish there. And I've added the paper to my reading list. Language design is fun, and I'm always looking for new good source material :)* The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment.You may want to reframe this as an opportunity to improve your understanding of subtyping. I recommend a classic paper by Cardelli and Wegner "On understanding Types, Data Abstraction, and Polymorphism" (http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf). It's a classic. Reading through section 1.5 does not require much background and is very rewarding.Again, I'd like to reaffirm my agreement that immutable objects are valuable. No disagreement from me there! I'm just used to an ecosystem where immutability is by convention, and the language-level immutability in D strikes me as somewhat heavy-handed.* The transitivity of const qualifiers requires me to consider not just the object I'm passing around, but the references to other objects contained within the const-qualified object.This is backwards, too. If const didn't exist or weren't transitive, you'd be required to keep in mind for mutation and aliasing bugs not just the object you're passing around, but the references to other objects.Yep! I love to engage in these kinds of discussions! And I hope you take this in the spirit of informed disagreement. I read about 90% of the messages in this NG, paying particular attention to the language-design discussions. I've taken the stuff I learned in this NG from geniuses like you and Walter, and used it in real-life projects, implementing a domain-specific language and compiler for one of my former employers. I'm sure you get tired of people throwing stones at your design concepts, especially without a detailed explanation. But I think many of us also get tired of being told "if you really understood, you'd agree". I may not fully understand all the details, but I understand enough to know that I'm not a fan. And I disagree about the "sausage-making". I've never successfully used a language feature, in any programming language, where I didn't understand the underlying mechanics of the implementation. Anyone writing code who doesn't know how it works under the hood is just a copy-paste programmer. Anyhow, I'll leave it at that. Since this community presumably exists to support a collaborative environment for advancing the D language (and does a remarkably good job of it in the vast majority of cases), I just wanted to put my vote in the ballot-box. :-) --benji* The equivariance proposal, at least in some of its forms, requires me to imagine the existence of implicit typedefs at the function-call site, and to know that the declared type in my method signature might not be the actual type of the arguments received by the function.Not at all. On this group many people have high competency and willing to discuss the most minute implementation details of a language feature, and my understanding is that you also engage in such discussions. On occasion, therefore, there will be discussion on exactly how sausages are being made. Do not confuse those discussions with things that complicate the experience of eating sausages.
Oct 15 2008
Benji Smith wrote:Hey, Andrei! First of all, just let me say how much I appreciate your detailed responses. I hope you don't ever take my feedback as a personal criticism or as being harshly negative. The only reason I've continued to engage is these kinds of debates for the last five years or so is because D interests me very much, and I'd like to see it meet its fullest potential. I know you have the same end-goals in mind, though we sometimes disagree about the most effective route from point A to point B.That is highly appreciated.Andrei Alexandrescu wrote:Sure. I suggested that to Walter two years ago, or maybe three. It was one of the first suggestion I made: you should make everything immutable by default, and mutable only if explicitly specified. (That's what e.g. ML and Cecil do.) He also liked it: instead of littering code with const this and immutable that, you only have var here and there - and maybe mutation is much less rarely needed than we think. In the end we rejected the idea because we thought it'll scare away people coming from C++ and Java. It was also too big a change from what D was at that point. So we had to drop that. But don't think for a moment that that approach would have been one iota simpler than what we have now. Supporting mutation and immutability in the same range is a sort of constant-sum game - you can pick your defaults, but overall the pie has the same size.I see where you're coming from. If your opinion is that the const system does not bring enough benefits to justify its learning, you are of course entitled to it. But talking about complexity implies that there would be simpler ways to achieve its charter that we missed, and talking about holes that we made mistakes of principle that go beyond bugs in the compiler or incompleteness of implementation.Yeah, I see where you're coming from too. I think you guys have done an admirable job thinking through the corner cases and figuring out ways to solve many of the sticky design issues that have arisen. On the other hand, there was a const proposal...maybe six months or a year ago...where all function parameters would be considered immutable by default, unless annotated as mutable.I thought that idea had a lot of merit. My first impression was that it'd provide more reliable guarantees, since it would be impossible to accidentally create a mutable param by forgetting to declare its constness. It also seemed like it would have resulted in more compact method signatures (since const arguments are generally more common than mutable arguments), and less overall risk of aliasing. For me, one of the main perks of the other proposal was less "const" litter all over the code, and yet with stronger const safety.You'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)Also, a function promising not to modify its arguments seems more like an attribute of the function, not of the arguments themselves.The signature of the function _is_ an attribute of the function.But I digress...It's a good digression.Driving a car with a manual transmission requires a more detailed mental model than driving an automatic. Maybe the manual transmission offers other advantages, but ease-of-use is not one of them.Hmmmm. I'd compare the wild west when everything is mutable at any time much more with an unwieldy manual-transmission car that leaks oil and blows a gasket every so often. That's congruent with my earlier point: you think const is more trouble for you. IMHO it's exactly, but I mean exactly, the opposite.I actually plan on giving D2 a spin as soon as a compatible version of Tango is available.[snip]But the const system just smells bad to me. I've tried to enumerate my reasons, and maybe switching to D2 will change my mind. But right now, I'm calling it as I see it. You asked for feedback, and you got it!!Now I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry. [snip]Since this community presumably exists to support a collaborative environment for advancing the D language (and does a remarkably good job of it in the vast majority of cases), I just wanted to put my vote in the ballot-box. :-)Please get information on the candidate before voting. Andrei
Oct 15 2008
Andrei Alexandrescu wrote:Now I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry. [snip]Point taken. I withdraw my comments about the const system, and from now on, I'll only critique stuff I've actually used, rather than proposals I've read and whose issues I've only imagined. And, as always, no harm no foul. --benjiSince this community presumably exists to support a collaborative environment for advancing the D language (and does a remarkably good job of it in the vast majority of cases), I just wanted to put my vote in the ballot-box. :-)Please get information on the candidate before voting.
Oct 15 2008
Hello Benji,Andrei Alexandrescu wrote:Sorry, can't help it: Bravo! Well done, you two! :) It's a joy to read posts that are respectful and considerate, yet without sacrificing a certain amount of directness. It a skill I'm still trying to develop. Thanks for setting the example, guys. -JJRNow I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry. [snip]Point taken. I withdraw my comments about the const system, and from now on, I'll only critique stuff I've actually used, rather than proposals I've read and whose issues I've only imagined. And, as always, no harm no foul. --benjiSince this community presumably exists to support a collaborative environment for advancing the D language (and does a remarkably good job of it in the vast majority of cases), I just wanted to put my vote in the ballot-box. :-)Please get information on the candidate before voting.
Oct 15 2008
John Reimer wrote:Hello Benji,Agreed. I'm telling you: this is so nice, I thought there for a moment that someone was impersonating me :o). AndreiAndrei Alexandrescu wrote:Sorry, can't help it: Bravo! Well done, you two! :) It's a joy to read posts that are respectful and considerate, yet without sacrificing a certain amount of directness. It a skill I'm still trying to develop. Thanks for setting the example, guys. -JJRNow I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry. [snip]Point taken. I withdraw my comments about the const system, and from now on, I'll only critique stuff I've actually used, rather than proposals I've read and whose issues I've only imagined. And, as always, no harm no foul. --benjiSince this community presumably exists to support a collaborative environment for advancing the D language (and does a remarkably good job of it in the vast majority of cases), I just wanted to put my vote in the ballot-box. :-)Please get information on the candidate before voting.
Oct 15 2008
Andrei Alexandrescu wrote:Benji Smith wrote:This has been an absolute godsend for writing D1->D2 portable code. So much so that I'm not sure I'd have even attempted it without this feature.For me, one of the main perks of the other proposal was less "const" litter all over the code, and yet with stronger const safety.You'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)I think being const-free results in more succinct code, and I'd argue it's more easily readable as well, because of the syntactic dissonance that the const labels introduce. But your wild west analogy is an apt one, because it makes data protection largely a matter of convention, while at least the switches and knobs of the manual transmission car provide a means to ensure safety at the design level. I think one could argue the benefits of each.Driving a car with a manual transmission requires a more detailed mental model than driving an automatic. Maybe the manual transmission offers other advantages, but ease-of-use is not one of them.Hmmmm. I'd compare the wild west when everything is mutable at any time much more with an unwieldy manual-transmission car that leaks oil and blows a gasket every so often. That's congruent with my earlier point: you think const is more trouble for you. IMHO it's exactly, but I mean exactly, the opposite.I've been a fairly vocal opponent of the const system in the past, largely because I'm not terribly happy with the complexity it can introduce in user code. However, I think it's important to note that I said /can/ rather than /will/. A great deal still comes down to programmer skill, and the rest, hopefully, will be addressed by discussions such as this. SeanI actually plan on giving D2 a spin as soon as a compatible version of Tango is available.[snip]But the const system just smells bad to me. I've tried to enumerate my reasons, and maybe switching to D2 will change my mind. But right now, I'm calling it as I see it. You asked for feedback, and you got it!!Now I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry.
Oct 15 2008
Andrei Alexandrescu wrote:You'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)What?? Whoa, at first I thought you were mistaken, and meant 'const(char[]) s' instead (since that is what is the same as 'const char[] s'), but I fired up my editor and tried it out, and it works as you described! Even more surprising, it works the same way when using a class type: class Foo { int x; } void func(in Foo foo, const scope Foo foo2) { foo = null; // Ok! //foo2 = null; //Compile error! //foo.x = 0; // Compile error! pragma(msg, (typeof(foo)).stringof ~ " " ~ (typeof(foo2)).stringof); } Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least. -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
On Sun, Oct 19, 2008 at 3:45 AM, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:Andrei Alexandrescu wrote:I think you mean "tailconst". The head is not const, the tail is. --bbYou'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)What?? Whoa, at first I thought you were mistaken, and meant 'const(char[]) s' instead (since that is what is the same as 'const char[] s'), but I fired up my editor and tried it out, and it works as you described! Even more surprising, it works the same way when using a class type: class Foo { int x; } void func(in Foo foo, const scope Foo foo2) { foo = null; // Ok! //foo2 = null; //Compile error! //foo.x = 0; // Compile error! pragma(msg, (typeof(foo)).stringof ~ " " ~ (typeof(foo2)).stringof); } Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least.
Oct 18 2008
Bill Baxter wrote:On Sun, Oct 19, 2008 at 3:45 AM, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:Yes, exactly, I meant 'tailconst'. -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DAndrei Alexandrescu wrote:I think you mean "tailconst". The head is not const, the tail is. --bbYou'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)What?? Whoa, at first I thought you were mistaken, and meant 'const(char[]) s' instead (since that is what is the same as 'const char[] s'), but I fired up my editor and tried it out, and it works as you described! Even more surprising, it works the same way when using a class type: class Foo { int x; } void func(in Foo foo, const scope Foo foo2) { foo = null; // Ok! //foo2 = null; //Compile error! //foo.x = 0; // Compile error! pragma(msg, (typeof(foo)).stringof ~ " " ~ (typeof(foo2)).stringof); } Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least.
Oct 19 2008
Bruno Medeiros Wrote:Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least.bug in docs. in can't be scope. docs: For dynamic array and object parameters, which are passed by reference, in/out/ref apply only to the reference and not the contents.
Oct 18 2008
Benji Smith wrote:On the other hand, there was a const proposal...maybe six months or a year ago...where all function parameters would be considered immutable by default, unless annotated as mutable.Just a minor correction: the proposal was for all function parameters to be *const* by default, not immutable. (it would be a pain if it was like that, immutable by default) -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
Benji Smith wrote:* Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... } const(char)[] join(const(char)[] str1, const(char)[] str1) { ... }Together with the existing 'string', I've created aliases of my own: 'mstring', short for mutable string, same as 'char[]', and 'cstring' for const string, same as 'const(char)[]'; I've found they bring not only less typing, but also less mental workload (like, easier to read). cstring join(cstring str1, cstring str1) { ... } Although that's mostly because of the fact that I don't have to immediately think of them as arrays of characters.* The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment.Like Andrei said, it does not violate the "is a" rule of type hierarchies. An invariant type can be used as a const type without breaking any of the contracts of invariant. Thus, it's a subtype as per the Liskov substitution principle. (and the same with mutable) -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
"Benji Smith" wroteSteven Schveighoffer wrote:B is definitely not the same as A. B could have a different implementation, I just didn't give it one. e.g.: class A : IClonable { typeof(this) clone() const { return new A(); } void foo() {writefln("This is A");} } class B : A { void foo() {writefln("This is B");} } B b = new B; b.clone().foo(); // prints "This is A" It can't work any other way, or else the type system would be broken. -Steveclass A : IClonable { typeof(this) clone() const { return new A();} } class B : A { } B b = new B; B x = b.clone(); Oops, b.clone() really only returns A, so this should fail. The real signature in A should be:No, b.clone() definitely returns an instance of B. It's purely coincidental (from the caller's perspective) that B's implementation is identical to that of A.
Oct 14 2008
Steven Schveighoffer wrote:"Benji Smith" wroteClone is different in two ways: 1. It must be implemented in *all* derived classes 2. For each of those classes, it must return the type of that class, not the type of any ancestor. Statically enforcing both 1 and 2 is the ideal case. Some languages allow it by allowing classes to express the actual (leaf) type of "this". D doesn't. I am not sure to what extent the feature is necessary. I would have been happy to capture it together with const-equivariance, but if that doesn't work, then what can I do. AndreiSteven Schveighoffer wrote:B is definitely not the same as A. B could have a different implementation, I just didn't give it one. e.g.: class A : IClonable { typeof(this) clone() const { return new A(); } void foo() {writefln("This is A");} } class B : A { void foo() {writefln("This is B");} } B b = new B; b.clone().foo(); // prints "This is A" It can't work any other way, or else the type system would be broken.class A : IClonable { typeof(this) clone() const { return new A();} } class B : A { } B b = new B; B x = b.clone(); Oops, b.clone() really only returns A, so this should fail. The real signature in A should be:No, b.clone() definitely returns an instance of B. It's purely coincidental (from the caller's perspective) that B's implementation is identical to that of A.
Oct 14 2008
"Andrei Alexandrescu" wroteSteven Schveighoffer wrote:I'm sure it can be done, but I wasn't thinking of that when you were suggesting equivariance. That sounds more like implementation rules (similar to if you implement an interface, you must implement all functions in the interface). What I was thinking of is putting into the signature of the function what the compiler has guaranteed about the return type. i.e. the return type is built from argument x. The typeof(this) seems like it has a single purpose though. Only as the return type of a class member function. It makes no sense as a struct member function, as you can't derive from it, and likewise for a free function. I think you are talking about a third feature other than the two I identified elsewhere. However, this might add to the usability of the 'return exact parameter' syntax. For example, for a function foo that takes a base type A, it can't guarantee that it can return type that was derived from A unless it returns the argument itself. But if an A class has the typeof(this) as one of its returns for a method, then A is guaranteeing that the return type is at least of the most derived type (and the compiler has already statically guaranteed that). So here's what I think we could do: 1. inout (or whatever keyword people like) defines how to deal with multiple constancies with one function. 2. typeof(this) as a return type on an interface or class method dictates that a. all derived classes must re-implement the given function b. the derived class must return the most derived type in that function. 3. typeof(arg) guarantees that either a. the function is returning that exact arg b. typeof(arg) is an interface or class, and the function is returning a result from arg.f, where arg.f returns typeof(this). In light of this possibility, I retract the proposal for return int x in the parameter list, as it would be difficult to declare a temporary variable of that type unambiguously. -Steve"Benji Smith" wroteClone is different in two ways: 1. It must be implemented in *all* derived classes 2. For each of those classes, it must return the type of that class, not the type of any ancestor. Statically enforcing both 1 and 2 is the ideal case. Some languages allow it by allowing classes to express the actual (leaf) type of "this". D doesn't. I am not sure to what extent the feature is necessary. I would have been happy to capture it together with const-equivariance, but if that doesn't work, then what can I do.Steven Schveighoffer wrote:B is definitely not the same as A. B could have a different implementation, I just didn't give it one. e.g.: class A : IClonable { typeof(this) clone() const { return new A(); } void foo() {writefln("This is A");} } class B : A { void foo() {writefln("This is B");} } B b = new B; b.clone().foo(); // prints "This is A" It can't work any other way, or else the type system would be broken.class A : IClonable { typeof(this) clone() const { return new A();} } class B : A { } B b = new B; B x = b.clone(); Oops, b.clone() really only returns A, so this should fail. The real signature in A should be:No, b.clone() definitely returns an instance of B. It's purely coincidental (from the caller's perspective) that B's implementation is identical to that of A.
Oct 14 2008
Steven Schveighoffer wrote:"Denis Koroskin" wroteNah, the whole goal is to have clone() in the base enforce that derivees implement it with the right signature... AndreiNow that I thought about it a little more (please, see and comment my post about typeof(this) nearby), I agree that the issues are related. However, the best solution could be a combination of both. For example, I agree that interface IClonable should be as follows: interface IClonable { typeof(this) clone() const; }No. IClonable should be: IClonable clone() const;
Oct 14 2008
On Wed, 15 Oct 2008 05:44:22 +0400, Steven Schveighoffer <schveiguy yahoo.com> wrote:"Denis Koroskin" wroteNope, you missed the idea. Was my post too long?Now that I thought about it a little more (please, see and comment my post about typeof(this) nearby), I agree that the issues are related. However, the best solution could be a combination of both. For example, I agree that interface IClonable should be as follows: interface IClonable { typeof(this) clone() const; }No. IClonable should be: IClonable clone() const; or if you prefer: Object clone() const; It can't be anything else, because IClonable cannot define how many levels deep it goes.For example, if I have: class A : IClonable { typeof(this) clone() const { return new A();} } class B : A { }This shouldn't compile - Error: class B doesn't implement typeof(this) clone() method.B b = new B; B x = b.clone();Error: can't instantiate abstract class B.Oops, b.clone() really only returns A, so this should fail. The real signature in A should be: class A : IClonable { A clone() const { return new A(); } }No, you missed the idea of the discussion.IClonable is a perfect candidate for covariant functions, which is already defined. -Steve
Oct 14 2008
Denis Koroskin wrote:Nope, you missed the idea. Was my post too long?Oops. You're right. Sorry about that :) --benji
Oct 14 2008
"Denis Koroskin" wroteOn Wed, 15 Oct 2008 05:44:22 +0400, Steven Schveighoffer <schveiguy yahoo.com> wrote:Yes, I did miss the idea. My apologies, I was not looking at equivariance meaning 'forcing derived classes to implement certain functions'. See my later post in reply to Andrei. Sorry for the confusion. -Steve"Denis Koroskin" wroteNope, you missed the idea. Was my post too long?Now that I thought about it a little more (please, see and comment my post about typeof(this) nearby), I agree that the issues are related. However, the best solution could be a combination of both. For example, I agree that interface IClonable should be as follows: interface IClonable { typeof(this) clone() const; }No. IClonable should be: IClonable clone() const; or if you prefer: Object clone() const; It can't be anything else, because IClonable cannot define how many levels deep it goes.
Oct 14 2008