digitalmars.D - Safer casts
- Janice Caron (66/66) May 09 2008 Better, safer casts have been requested before - in fact I routinely
- terranium (6/12) May 09 2008 1) I don't think you can throw away the cast keyword, this will break al...
- Janice Caron (29/35) May 09 2008 This is exactly the problem C++ had. C++ introduced new, better cast
- terranium (4/21) May 09 2008 My opinion is we don't need to be explicit when casting double to int. r...
- terranium (1/1) May 09 2008 and I like existing cast keyword, it's short enough to write and long en...
- Janice Caron (27/28) May 09 2008 I like it too. But it has its disadvantages. For example, you might
- terranium (1/1) May 09 2008 And I hate those exclamation marks and chains of brackets.
- terranium (2/4) May 09 2008 well, this is a good example.
- janderson (7/102) May 09 2008 I think that up casting for classes should be implicit, like it works in...
- Robert Fraser (2/106) May 09 2008 I think Janice mentioned that it would work exactly like that.
- Janice Caron (2/7) May 09 2008 Yes, you are completely correct, of course.
- BCS (8/41) May 09 2008 The major issue I have with this is that the construct that actually
- Janice Caron (4/9) May 09 2008 OK, so how about just making class!(T) return null, and do away with
- Janice Caron (7/11) May 09 2008 One goal of the proposal was to avoid introducing more keywords. Some
- BCS (4/19) May 09 2008 I think it you go with the __!(T) syntax it would be better to /not/
- Yigal Chripun (27/27) May 09 2008 I too do not like the proposed syntax. I'd suggest the following scheme:
- Janice Caron (37/42) May 09 2008 Your proposal is better than mine, but I think I can improve it even mor...
- Dee Girl (3/16) May 09 2008 With much interest I followed this thread. Forgive my ignorance, but I c...
- Janice Caron (19/20) May 09 2008 Not really, since part of the proposal is that
- Dee Girl (9/25) May 09 2008 But in C++ I can not grep for old-style cast. In D I can (but see my oth...
- Janice Caron (24/25) May 09 2008 That still doesn't help. You can stare at a bug for ages, and still
- Simen Kjaeraas (5/8) May 11 2008 For 3), what if I want to cast const(int*) to const(int)*?
- Janice Caron (19/27) May 11 2008 Wow, we're back on topic again! Woo hoo!
- Yigal Chripun (21/60) May 11 2008 another option would be to allow:
- Janice Caron (5/9) May 11 2008 Oh, I see. So, cast(T) would double up for /either/ downcasting, /or/
- Yigal Chripun (5/16) May 11 2008 Actually, we got it slightly wrong:
- Janice Caron (22/24) May 11 2008 Hmmm. Perhaps. But then you lose the distinction between const_cast
- Yigal Chripun (13/44) May 11 2008 I'm not sure your syntax can be used since those two words already have
- Janice Caron (73/86) May 11 2008 Yes, which is why I now suggest
- Yigal Chripun (26/26) May 11 2008 Janice Caron wrote:
- Janice Caron (68/91) May 11 2008 Agreed. As a general principle, if it can be implemented in a library,
- Yigal Chripun (8/8) May 11 2008 Janice Caron wrote:
- Yigal Chripun (2/7) May 11 2008 that looks correct.
- Steven Schveighoffer (13/20) May 12 2008 I'm not really joining this discussion (yet), but I just wanted to respo...
- Simen Kjaeraas (12/15) May 12 2008 Actually, I had no other intent than to see if I could break the system.
- Yigal Chripun (9/32) May 12 2008 if I'm not mistaken, the other example was both a cast of constancy and
- Yigal Chripun (24/24) May 09 2008 thanks for the added examples and explanations to the proposal.
- Janice Caron (40/46) May 10 2008 Right. But in my view cast! (with an exclamation mark) can never fail
- Yigal Chripun (20/20) May 10 2008 I do not agree with the premise that a reinterpret cast always succeeds,
- Janice Caron (35/40) May 10 2008 I have to say yes.
- terranium (2/6) May 10 2008 this makes string array of bytes rather than chars.
- Janice Caron (11/17) May 10 2008 You are incorrect. Indisputably, typeof(t) == invariant(char)[]. It is
- terranium (2/3) May 10 2008 I'll let others to have the pleasure to discover this themselves :)))
- Janice Caron (6/13) May 10 2008 My apologies. The complete string consists of /one/ malformed code
- Yigal Chripun (43/43) May 10 2008 IMHO, your reply makes perfect sense for C/C++ but not for D.
- Dee Girl (6/27) May 10 2008 I want to understand what you said because it can change my decision to ...
- Sean Kelly (23/24) May 10 2008 I'm somewhat biased since I created the module, but I'd consider tango.c...
- Dee Girl (2/33) May 10 2008 Nice example! How did you do it? Did Tango change the compiler and added...
- Janice Caron (18/19) May 10 2008 Yeah, but look what you can do with Phobos.
- Yigal Chripun (13/39) May 10 2008 here comes my personal emotional response: I hate the above code! it
- Dee Girl (5/44) May 10 2008 I agree! Strings and aliases are much less powerful than delegates and c...
- Yigal Chripun (4/13) May 10 2008 I'm not sure what exactly are you asking. are you asking what's the
- Dee Girl (2/15) May 10 2008 Please let me rephrase. What is the difference between a function passed...
- BCS (4/25) May 10 2008 a function is just a free function or a static class function or the lik...
- Dee Girl (25/52) May 10 2008 Sorry I do not know how to express myself. It is frustrating ^_^.
- Yigal Chripun (29/29) May 11 2008 Your example does use delegates.
- Janice Caron (8/11) May 11 2008 C++ has functors (classes which overload operator ()), and they're a
- Koroskin Denis (3/4) May 11 2008 I like this one, too. It's very simple, efficent and clean.
- Yigal Chripun (15/21) May 11 2008 In what way is that clean? How is that different from a C macro?
- Janice Caron (11/14) May 11 2008 On that point, I concede. The reporting of template errors could
- Yigal Chripun (15/32) May 11 2008 are we now debating the "sufficiently smart compiler" problem? ;)
- Leandro Lucarella (16/33) May 12 2008 I think you can use Token Strings[1] in D2 to have better error reportin...
- Yigal Chripun (41/66) May 12 2008 this was already debated thoroughly, But I'll state my POV again. I
- Janice Caron (13/20) May 12 2008 ints don't have a member variable called .member, so presumably, you
- Yigal Chripun (29/62) May 12 2008 again, obvious to you, not that dude five years from now reading your
- lurker (1/1) May 12 2008 Somebody please throw Yigal's towel before Janice and the karate girl be...
- Janice Caron (25/40) May 12 2008 I'm talking about the documentation for std.algorithm.sort. What are
- Yigal Chripun (18/84) May 12 2008 Again, this is exactly what I want. this should be in the standard libra...
- Janice Caron (26/34) May 13 2008 alias std.algorithm.sort!("b
- Yigal Chripun (16/65) May 13 2008 I want this alias in the library. I'd prefer a sort that received some
- Janice Caron (9/16) May 13 2008 It can't be in the library, because if it were in the library it would
- Yigal Chripun (11/34) May 13 2008 either D should provide special treatment for this or it shoud use one
- Janice Caron (3/6) May 13 2008 If you'd read a few posts on you would have realised I corrected that
- Frits van Bommel (4/11) May 13 2008 And if you'd replied to your own post with that correction instead of
- Janice Caron (3/7) May 13 2008 My apologies - I think we both meant "override", not "overload". :-)
- Yigal Chripun (2/12) May 13 2008 agreed :)
- Sean Kelly (13/49) May 13 2008 Wow, then tango.core.Array.sort must work by magic! I had no idea Tango...
- Dee Girl (3/19) May 12 2008 There are many things you write and then write is your opinion and other...
- Yigal Chripun (23/23) May 12 2008 Dee Girl wrote:
- Dee Girl (8/31) May 12 2008 Good. I think I understand. I want to be very sure, so I have a question...
- Sean Kelly (10/41) May 12 2008 It depends what you mean by "powerful." Passing a comparator as a templ...
- Dee Girl (3/45) May 12 2008 Question was not about inlining. It was about indirect call with a point...
- Sean Kelly (16/61) May 12 2008 works for a dynamic delegate dg. Just try it! It was hard to believe for...
- Dee Girl (2/67) May 12 2008 Thank you for the information. But still there is no answer to my questi...
- Yigal Chripun (30/32) May 13 2008 well, you've been answered by an expert (Thanks Sean!)
- Dee Girl (8/43) May 13 2008 I think this is great but it is much easy said than done. Inlining an in...
- Yigal Chripun (17/72) May 13 2008 OK. so what?
- Dee Girl (6/58) May 13 2008 I am using standard terminology. But maybe my sentences are wrong.
- Yigal Chripun (25/36) May 13 2008 again, I'll leave this to Walter to figure out. the decision to inline
- Janice Caron (12/13) May 13 2008 Please don't confuse inlining with instantiation. This does not help
- Yigal Chripun (13/33) May 13 2008 you know, it's not easy to have two parallel conversations with two
- Sean Kelly (5/24) May 13 2008 In fact, I think this feature was probably added specifically because
- Sean Kelly (5/34) May 18 2008 I take it back. I created some test code for D 1.0 maybe a year ago
- Sean Kelly (14/38) May 13 2008 Regarding the alias stuff I didn't know before. I don't suppose you
- Bruno Medeiros (14/25) Jun 10 2008 Hum, I thought the same. And frankly I couldn't see how it could work
- Janice Caron (5/8) May 13 2008 I think you meant to say, the choice of comparator /may/ be made at
- Dee Girl (5/15) May 13 2008 Even more simple.
- Sean Kelly (5/15) May 13 2008 I meant the way sort works in std.algorithm. But it appears that my
- Don (3/20) May 13 2008 It should work in D1.0, as well. It was added not long before D1.00 came...
- Sean Kelly (12/32) May 13 2008 It doesn't. Or more specifically, this doesn't work in D 1.0:
- Bill Baxter (5/40) May 13 2008 Oh, *that's* what you guys are talking about? Here's the bugzilla bug
- Sean Kelly (4/45) May 13 2008 And here I thought D 1.0 just didn't like my aliasing a local variable.
- Janice Caron (6/11) May 13 2008 The code inside sort!() /is/ a delegate. It's an anonymous delegate,
- Dee Girl (2/17) May 13 2008 Yes, excellent point. The codes are equivalent. But foo is not a delegat...
- Yigal Chripun (6/24) May 13 2008 foo _is_ a delegate.
- Dee Girl (3/28) May 13 2008 It is clear what is a delegate. It is pointer to a function and pointer ...
- Yigal Chripun (13/28) May 13 2008 sure. Call it what you like, the point is that you can pass foo to a
- Dee Girl (3/33) May 13 2008 Then I am sure he wish Pascal did not have nested functions ^_^ PS I ins...
- Yigal Chripun (21/33) May 13 2008 the main thing for me is the syntax:
- Janice Caron (20/39) May 13 2008 And the same thing in D:
- Yigal Chripun (3/50) May 13 2008 exactly. the point was for Dee about other languages that provide the
- Dee Girl (4/54) May 13 2008 I do not know Nemerle. But the way run_twice looks it is not parameteriz...
- Janice Caron (3/4) May 13 2008 Darn copy/paste errors. I meant of course
- Koroskin Denis (8/13) May 13 2008 No, it is not. It's just a function, that takes additional
- Janice Caron (3/6) May 13 2008 I'm sure Yigal intended to mean "&foo _is_ a delegate". I understood
- Yigal Chripun (2/9) May 13 2008 :-)
- Dee Girl (25/63) May 12 2008 algorithm.d(2528): Error: binaryFun(ElementType1,ElementType2) is not an...
- Yigal Chripun (89/209) May 12 2008 But it doesn't. also, the file name and line number are wrong. that IS
- Dee Girl (17/183) May 12 2008 This argument may be not correct. It is true code is read many times but...
- Bill Baxter (13/56) May 12 2008 All of these things will cause compile-time errors. You are right that
- Yigal Chripun (7/70) May 12 2008 i know it's a string mixin. my personal issue with it is indeed the
- Ary Borenszweig (15/28) May 12 2008 I mostly agree with you. The problem with defining these things is
- Yigal Chripun (3/19) May 12 2008 Good Points! I thought about the IDE issue, but I wasn't sure how the
- Janice Caron (9/11) May 13 2008 I don't have an IDE, but in my text editor,
- Yigal Chripun (3/19) May 13 2008 OK, but the point Ary made was that an IDE is smart and provides
- Ary Borenszweig (7/23) May 13 2008 That's just because normally most editors are configured so that "" and
- Yigal Chripun (19/34) May 11 2008 it mis-uses templates, as I said in my previous post. it's the same C++
- Janice Caron (15/23) May 11 2008 But now you're just saying "my syntax is better than your syntax",
- Bill Baxter (7/13) May 11 2008 Is it? If so that's great. Last comment I recall from Walter on the
- Yigal Chripun (32/32) May 11 2008 you miss the point. it's not just a matter of syntax it's a matter of
- Janice Caron (26/42) May 11 2008 I may be missing /a/ point, but I doubt I'm missing /the/ point. Who
- Yigal Chripun (28/88) May 11 2008 I want to be able to do this:
- Janice Caron (43/60) May 11 2008 Umm. Yes.
- Yigal Chripun (43/127) May 11 2008 right...
- Janice Caron (33/67) May 11 2008 List!(T) list;
- Koroskin Denis (23/37) May 11 2008 =
- Yigal Chripun (4/48) May 11 2008 all I can say is yuk. i can use tricks like that, or i can just use D's
- Christopher Wright (15/17) May 11 2008 Problem with using interfaces or base classes is, you can't do the
- terranium (3/7) May 11 2008 does it have any use?
- Dee Girl (19/48) May 11 2008 This should be good for the standard library. It is the most flexible ap...
- Yigal Chripun (33/105) May 11 2008 not exactly. there's always a balance between memory and time, if you
- Janice Caron (30/40) May 11 2008 But surely, every container must itself be templatised on the element?
- Yigal Chripun (23/81) May 11 2008 From what I know, an efficient STL implementation would use C style
- Janice Caron (20/22) May 11 2008 I /think/ I get what you're saying here, which is (correct me if I'm
- Yigal Chripun (23/53) May 11 2008 I don't know about any plans to change the dm linker. it is currently
- Janice Caron (11/14) May 11 2008 You do now. :-)
- Yigal Chripun (12/32) May 11 2008 I know about that quote. that doesn't mean anything. saying that
- Yigal Chripun (8/12) May 11 2008 (light-bulb)
- Dee Girl (8/114) May 11 2008 I do not think so. In fact I am sure you are entirely wrong this time.
- Yigal Chripun (20/20) May 11 2008 your reply is correct but it is misleading.
- Janice Caron (21/26) May 11 2008 Whose reply? What is this a reply to?
- Yigal Chripun (6/47) May 11 2008 I don't follow. How can there be only one sort instance?
- Janice Caron (14/18) May 11 2008 Because I only instantiated it once - specifically with the delegate
- Dee Girl (2/14) May 11 2008 Thank you. This is a clear explaining. It took me long time to write my ...
- Yigal Chripun (4/33) May 11 2008 OK, I think i understand you now. there is only one sort instance that
- Dee Girl (48/68) May 11 2008 I am sorry Yigal. What you say makes no sense. Your previous reply is wr...
- Yigal Chripun (4/4) May 11 2008 Dee Girl wrote:
- Yigal Chripun (59/59) May 10 2008 Disclaimer: this post reflects my personal opinion only and should not
- Me Here (3/5) May 10 2008 D'accord.
- Yigal Chripun (21/69) May 10 2008 I think I misread your example so I want to clarify:
- Janice Caron (4/5) May 10 2008 That has never been the case, ever. If you think that D should change,
- terranium (2/5) May 10 2008 current implementation of cast already does the typecheck, so there will...
- BCS (4/13) May 10 2008 but the proposal would requier *two* checks in the "use this as a B if i...
- Robert Fraser (27/99) May 09 2008 I'm sorry if this post sounds argumentative, but I disagree strongly
- Robert Fraser (22/23) May 09 2008 Also, I find the cast-returns-null-on-failure behavior quite useful. In
- terranium (4/30) May 10 2008 ... should become ... :))))
- Janice Caron (22/38) May 09 2008 All is peaceful here on the newsgroup. This is just a nice, friendly
-
Bruce Adams
(49/93)
May 09 2008
On Fri, 09 May 2008 19:58:49 +0100, Janice Caron
- terranium (2/4) May 10 2008 I agree here. I think that language should be simple and understandable,...
- terranium (2/6) May 10 2008 This is a really hard task. Proper design is probably the hardest thing ...
- terranium (2/4) May 10 2008 D foreach is very advanced and expressive in this. Though not in ruby wa...
- Dee Girl (3/30) May 11 2008 I am not sure I understood all how D templates work. But how it seems is...
- Yigal Chripun (11/24) May 11 2008 read my other reply about time vs. space.
- Janice Caron (13/20) May 11 2008 If the function is called N times (where N > 0), the template is
- Yigal Chripun (16/48) May 11 2008 if what you say is true ( and i don't have any reason to believe
- Dee Girl (2/11) May 11 2008 It is fortunate in my humble opinion that the designers of D think other...
- Yigal Chripun (27/49) May 11 2008 I was only referring to Eiffel's collection framework which i think is a
- Leandro Lucarella (32/59) May 12 2008 I don't think it's a good idea, because you end up doing the RTTI check
Better, safer casts have been requested before - in fact I routinely see that request on the monthly "D wishlist" that goes round. But here's a fairly concrete suggestion as to what the syntax might be. And as a bonus, we'd get rid of the "cast" keyword, thus reducing the keyword count by one. So, here are the various types of transformation I suggest: (*) Build a new instance with a different memory layout, but with the same value. (For example, casting double to int). For lossless conversions, this can happen implicitly, but for lossy conversions, an explicit cast is necessary. For D, I suggest to!(T)(x) where to! is a template function provided by std.conv (not a keyword). to! already performs many conversions. Throwing in the basics like to!(int)(double) would be an obvious extension. (*) Use RTTI to cast up and down the class heirarchy. In C++, this would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the cast: class!(T)(x) to do that actual upcast or downcast - but RTTI dynamic casts can fail, so the question arises: What should we do if the cast fails? Should we return null, or should we throw an exception. My view is that we should throw an exception, but also introduce a new construct to test whether or not the cast would be possible in the first place: is!(T)(x) which returns bool. is!(T) would be like Java's instanceof. Thus, one could write void f(A a) { if (is!(B)a) { B b = class!(B)(a); /*...*/ } else if (is!(C)a) { C c = class!(C)(a); /*...*/ } } etc. (*) Reinterpret a bit pattern. In C++, this would be reinterpret_cast<T>(x). Without changing the memory layout, or the constancy, reinterpret the bits to mean something else. For D, I suggest union!(T)(x) (*) Change const to mutable, or invariant to mutable. In C++, this would be const_cast<T>(x). There is no equivalent in D, however, in D, one can currently write cast(T)x, and constancy will be magically (and dangerously) waved away. In the new scheme, I suggest: auto!(T)(x) (*) Change const to invariant, or mutable to invariant. Currently, the syntax for this is cast(invariant)x, however this has two disadvantages: (i) the entire type is made invariant, and you might want to invariantize only part of the type, and (ii) cast(T)x also works. In other words, (i) is not powerful enough, and (ii) is too dangerous. There is no equivalent for this in C++, since C++ has no invariance. For D, I suggest: invariant!(T)(x) I think that's all bases covered. So in summary we'd have: to!(T)(x) // convert is!(T)(x) // is upcast or downcast possible? class!(T)(x) // upcast or downcast union!(T)(x) // reinterpret the bits auto!(T)(x) // remove constancy invariant!(T)(x) // add invariance Those seem reasonably obvious word choices to me. Thoughts?
May 09 2008
Janice Caron Wrote:to!(T)(x) // convert is!(T)(x) // is upcast or downcast possible? class!(T)(x) // upcast or downcast union!(T)(x) // reinterpret the bits auto!(T)(x) // remove constancy invariant!(T)(x) // add invariance1) I don't think you can throw away the cast keyword, this will break all existing code. 2) how about interfaces? 3) I believe reinterpret_cast was introduced as analog of C cast, D already has cast for this purpose. I don't think D needs union!(). 4) D already has cast to add/remove constancy/invariance. My proposal was just adding safe cast for downcasts with the syntax of existing cast, nothing more.
May 09 2008
2008/5/9 terranium <spam here.lot>:1) I don't think you can throw away the cast keyword, this will break all existing code.This is exactly the problem C++ had. C++ introduced new, better cast types: static_cast<T>, dynamic_cast<T>, const_cast<T>, and reinterpret_cast<T>. However, where C++ went wrong is they failed to outlaw the original cast - presumably for the same reason, that it would break existing code. The problem is that unless you outlaw the old "one cast for all purposes" cast, then people will continue to use it, when what you really want it to force everyone to migrate to the new, "safe" ways.2) how about interfaces?They're a run-time thing, so is!(T) and class!(T) should work for interfaces just as for classes. This is where D differs from C++, of course, because we have interfaces where C++ has multiple inheritance. But yeah - just as C++'s dynamic_cast<T> works for multiple inheritance, so class!(T) should work for interfaces - though I note that the name is now less apt.3) I believe reinterpret_cast was introduced as analog of C cast,It was introduced to replace /one/ kind of transformation which old-style casts did, but not all of them. For example // C++ double x; int y = reinterpret_cast<int>(x); // won't compile int y = static_cast<int>(x); // OKD already has cast for this purpose. I don't think D needs union!().D already has cast for /all/ of the purposes I listed, so you could argue that D doesn't need any of them. The point is, if you wanted to be explicit about exactly what kind of transformation you wanted, then you would need it.4) D already has cast to add/remove constancy/invariance.Again, D already has cast, which can do each and every transformation I listed. What it can't do is emit an error if the wrong kind of transformation is performed.My proposal was just adding safe cast for downcasts with the syntax of existing cast, nothing more.I wasn't disputing that. Consider this a separate proposal.
May 09 2008
Janice Caron Wrote:I note that the name is now less apt.that's what I meant.My opinion is we don't need to be explicit when casting double to int. reinterpret_cast is used usually to cast to/from void* - that is for pointer types - here it has no difference with D cast. Adding new construct adds complexity and requires extra time to learn and extra info to keep in mind, I believe these constructs' safeness is minor and it's not worth its complexity.3) I believe reinterpret_cast was introduced as analog of C cast,It was introduced to replace /one/ kind of transformation which old-style casts did, but not all of them. For example // C++ double x; int y = reinterpret_cast<int>(x); // won't compile int y = static_cast<int>(x); // OK D already has cast for /all/ of the purposes I listed, so you could argue that D doesn't need any of them. The point is, if you wanted to be explicit about exactly what kind of transformation you wanted, then you would need it.I wasn't disputing that. Consider this a separate proposal.I meant my proposal is better :))) We need only minor extra safeness. This can be achieved just by standardizing safe cast.
May 09 2008
and I like existing cast keyword, it's short enough to write and long enough to spot.
May 09 2008
2008/5/9 terranium <spam here.lot>:and I like existing cast keyword, it's short enough to write and long enough to spot.I like it too. But it has its disadvantages. For example, you might write a dynamic cast as void f(in A a) { B b = cast(B) a; if (b !is null) { /* ... */ } /*...*/ } and it would compile. Unfortunately, you have just accidently cast away const, and that kind of bug is hard to spot. But replace it with void f(in A a) { if (is!(B)(a)) { B b = class!(B)(a); /* ... */ } /*...*/ } and you have a compile-time error, because class!(T) cannot change const.into mutable. So I guess there's a tradeoff between "nice" syntax, like cast(T), and "safe" syntax. I'm just wondering if we should err on the side of safe
May 09 2008
And I hate those exclamation marks and chains of brackets.
May 09 2008
Janice Caron Wrote:and it would compile. Unfortunately, you have just accidently cast away const, and that kind of bug is hard to spot. But replace it withwell, this is a good example.
May 09 2008
Janice Caron wrote:Better, safer casts have been requested before - in fact I routinely see that request on the monthly "D wishlist" that goes round. But here's a fairly concrete suggestion as to what the syntax might be. And as a bonus, we'd get rid of the "cast" keyword, thus reducing the keyword count by one. So, here are the various types of transformation I suggest: (*) Build a new instance with a different memory layout, but with the same value. (For example, casting double to int). For lossless conversions, this can happen implicitly, but for lossy conversions, an explicit cast is necessary. For D, I suggest to!(T)(x) where to! is a template function provided by std.conv (not a keyword). to! already performs many conversions. Throwing in the basics like to!(int)(double) would be an obvious extension. (*) Use RTTI to cast up and down the class heirarchy. In C++, this would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the cast: class!(T)(x) to do that actual upcast or downcast - but RTTI dynamic casts can fail, so the question arises: What should we do if the cast fails? Should we return null, or should we throw an exception. My view is that we should throw an exception, but also introduce a new construct to test whether or not the cast would be possible in the first place: is!(T)(x) which returns bool. is!(T) would be like Java's instanceof. Thus, one could write void f(A a) { if (is!(B)a) { B b = class!(B)(a); /*...*/ } else if (is!(C)a) { C c = class!(C)(a); /*...*/ } } etc. (*) Reinterpret a bit pattern. In C++, this would be reinterpret_cast<T>(x). Without changing the memory layout, or the constancy, reinterpret the bits to mean something else. For D, I suggest union!(T)(x) (*) Change const to mutable, or invariant to mutable. In C++, this would be const_cast<T>(x). There is no equivalent in D, however, in D, one can currently write cast(T)x, and constancy will be magically (and dangerously) waved away. In the new scheme, I suggest: auto!(T)(x) (*) Change const to invariant, or mutable to invariant. Currently, the syntax for this is cast(invariant)x, however this has two disadvantages: (i) the entire type is made invariant, and you might want to invariantize only part of the type, and (ii) cast(T)x also works. In other words, (i) is not powerful enough, and (ii) is too dangerous. There is no equivalent for this in C++, since C++ has no invariance. For D, I suggest: invariant!(T)(x) I think that's all bases covered. So in summary we'd have: to!(T)(x) // convert is!(T)(x) // is upcast or downcast possible? class!(T)(x) // upcast or downcast union!(T)(x) // reinterpret the bits auto!(T)(x) // remove constancy invariant!(T)(x) // add invariance Those seem reasonably obvious word choices to me. Thoughts?I think that up casting for classes should be implicit, like it works in C++. Its perfectly ok to upcast and there shouldn't be any warning casts around that. That is actually a very useful feature for genetic style coding. Of course the cast should still work for upcasting, but its superfluous. -Joel
May 09 2008
janderson wrote:Janice Caron wrote:I think Janice mentioned that it would work exactly like that.Better, safer casts have been requested before - in fact I routinely see that request on the monthly "D wishlist" that goes round. But here's a fairly concrete suggestion as to what the syntax might be. And as a bonus, we'd get rid of the "cast" keyword, thus reducing the keyword count by one. So, here are the various types of transformation I suggest: (*) Build a new instance with a different memory layout, but with the same value. (For example, casting double to int). For lossless conversions, this can happen implicitly, but for lossy conversions, an explicit cast is necessary. For D, I suggest to!(T)(x) where to! is a template function provided by std.conv (not a keyword). to! already performs many conversions. Throwing in the basics like to!(int)(double) would be an obvious extension. (*) Use RTTI to cast up and down the class heirarchy. In C++, this would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the cast: class!(T)(x) to do that actual upcast or downcast - but RTTI dynamic casts can fail, so the question arises: What should we do if the cast fails? Should we return null, or should we throw an exception. My view is that we should throw an exception, but also introduce a new construct to test whether or not the cast would be possible in the first place: is!(T)(x) which returns bool. is!(T) would be like Java's instanceof. Thus, one could write void f(A a) { if (is!(B)a) { B b = class!(B)(a); /*...*/ } else if (is!(C)a) { C c = class!(C)(a); /*...*/ } } etc. (*) Reinterpret a bit pattern. In C++, this would be reinterpret_cast<T>(x). Without changing the memory layout, or the constancy, reinterpret the bits to mean something else. For D, I suggest union!(T)(x) (*) Change const to mutable, or invariant to mutable. In C++, this would be const_cast<T>(x). There is no equivalent in D, however, in D, one can currently write cast(T)x, and constancy will be magically (and dangerously) waved away. In the new scheme, I suggest: auto!(T)(x) (*) Change const to invariant, or mutable to invariant. Currently, the syntax for this is cast(invariant)x, however this has two disadvantages: (i) the entire type is made invariant, and you might want to invariantize only part of the type, and (ii) cast(T)x also works. In other words, (i) is not powerful enough, and (ii) is too dangerous. There is no equivalent for this in C++, since C++ has no invariance. For D, I suggest: invariant!(T)(x) I think that's all bases covered. So in summary we'd have: to!(T)(x) // convert is!(T)(x) // is upcast or downcast possible? class!(T)(x) // upcast or downcast union!(T)(x) // reinterpret the bits auto!(T)(x) // remove constancy invariant!(T)(x) // add invariance Those seem reasonably obvious word choices to me. Thoughts?I think that up casting for classes should be implicit, like it works in C++. Its perfectly ok to upcast and there shouldn't be any warning casts around that. That is actually a very useful feature for genetic style coding. Of course the cast should still work for upcasting, but its superfluous. -Joel
May 09 2008
On 09/05/2008, janderson <askme me.com> wrote:I think that up casting for classes should be implicit for up cast, like it works in C++. Its perfectly ok to upcast and there shouldn't be any warning casts around that. That is actually a very useful feature for genetic style coding. Of course the cast should still work for upcasting, but its superfluous.Yes, you are completely correct, of course.
May 09 2008
Janice Caron wrote:(*) Use RTTI to cast up and down the class heirarchy. In C++, this would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the cast: class!(T)(x) to do that actual upcast or downcast - but RTTI dynamic casts can fail, so the question arises: What should we do if the cast fails? Should we return null, or should we throw an exception. My view is that we should throw an exception, but also introduce a new construct to test whether or not the cast would be possible in the first place: is!(T)(x) which returns bool. is!(T) would be like Java's instanceof. Thus, one could write void f(A a) { if (is!(B)a) { B b = class!(B)(a); /*...*/ } else if (is!(C)a) { C c = class!(C)(a); /*...*/ } } etc.The major issue I have with this is that the construct that actually does the downcast MUST also do the type check. Therefor it gets done twice and this is a bit of a performance issue. (and performance snobs will start finding way to skip the second test (like your union) and then get it wrong.) I'd rather see a testClass!(T) version that acts exactly like the current cast and a isClass!(T) that acts like your class!(T).
May 09 2008
On 09/05/2008, BCS <BCS pathlink.com> wrote:The major issue I have with this is that the construct that actually does the downcast MUST also do the type check. Therefor it gets done twice and this is a bit of a performance issue.Good point.I'd rather see a testClass!(T) version that acts exactly like the current cast and a isClass!(T) that acts like your class!(T).OK, so how about just making class!(T) return null, and do away with is!(T)? That's what C++ does with dynamic_cast<T>, after all.
May 09 2008
On 09/05/2008, Janice Caron <caron800 googlemail.com> wrote:> I'd rather see a testClass!(T) version that acts exactly like the current > cast and a isClass!(T) that acts like your class!(T). OK, so how about just making class!(T) return null, and do away with is!(T)? That's what C++ does with dynamic_cast<T>, after all.One goal of the proposal was to avoid introducing more keywords. Some of these conversions don't have to be keywords, of course. to! is already implemented in std.conv, and this proposal only suggests extending its reach. But others kinds of transformation are better done by the compiler, and I'd say that includes RTTI, so for that reason, I went for keyword reuse.
May 09 2008
Janice Caron wrote:On 09/05/2008, Janice Caron <caron800 googlemail.com> wrote:I think it you go with the __!(T) syntax it would be better to /not/ used keywords at all. Make them either "magic" template that are defined in the compiler or as part of the compiler specific runtime.One goal of the proposal was to avoid introducing more keywords. Some of these conversions don't have to be keywords, of course. to! is already implemented in std.conv, and this proposal only suggests extending its reach. But others kinds of transformation are better done by the compiler, and I'd say that includes RTTI, so for that reason, I went for keyword reuse.I'd rather see a testClass!(T) version that acts exactly like the current cast and a isClass!(T) that acts like your class!(T).OK, so how about just making class!(T) return null, and do away with is!(T)? That's what C++ does with dynamic_cast<T>, after all.
May 09 2008
I too do not like the proposed syntax. I'd suggest the following scheme: 1) convert: cast(foo)bar 2) reinterpret: cast!(foo)bar 3) constancy: cast(invariant), cast(mutable) 4) downcast: cast!(foo)bar explanation: all casts use the word "cast" so it's easy to grep for. more dangerous casts use the "cast!" form. reinterpret and downcast both do the same thing therefore there's no need to separate the two (both do not change the actual memory). Constancy is cast explicitly with its own cast instance. example of use: invariant int a = ...; double a = cast!(double)cast(mutable)(b); the above removes invariance and then reinterprets the bit pattern to double. class B : A {} class C : A {} A b = new B; C c = cast!(C)b; // c is null cast! is more dangerous and thus can fail and return a null as in the above example. OTOH, cast is safe and expected to always work, so if you use cast instead of cast! to downcast, an exception will be thrown. have I left anything out? -- Yigal PS - note that my proposal only adds the "cast!" form to D. cast(invariant) is already defined in D, and cast(mutable) can be replaced by cast(!invariant) although it's less readable IMO.
May 09 2008
On 10/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I too do not like the proposed syntax. I'd suggest the following scheme: 1) convert: cast(foo)bar 2) reinterpret: cast!(foo)bar 3) constancy: cast(invariant), cast(mutable) 4) downcast: cast!(foo)barYour proposal is better than mine, but I think I can improve it even more: 1) convert: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)bar 4) downcast: cast(Foo)bar There are just two differences. I suggest cast(!const) to remove constancy, because cast(mutable) could be ambiguous if there happened to be a type called "mutable" (unless you're suggesting "mutable" be a reserved word, but Walter would never allow that). Downcasting isn't particularly dangerous, providing you always check the return value, so no exclamation mark needed there. The important point about this proposal is that the following will /not/ compile: const c = new C; auto d = cast(C)c; // ERROR - cast cannot remove constancy But this will const c = new C; auto d = cast(!const)c; // OK Likewise with downcasting void foo(in A a) { B b = cast(B)a; // ERROR - cast cannot remove constancy if (b !is null) ... } but the following would be OK void foo(in A a) { B b = cast(B)cast(!const)a; // OK if (b !is null) ... } although of course, what you probably should be doing is really: void foo(in A a) { const B b = cast(const B)a; // OK if (b !is null) ... }
May 09 2008
Janice Caron Wrote:On 10/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:With much interest I followed this thread. Forgive my ignorance, but I can not understand one thing. Could this functionality be implemented as library functions all using the built in cast? The syntax will be clumsier though. I see from std.typecons and std.typetraits that D has powerful meta programming. Functions should be able to detect different cases. Coding standards could prescribe that cast must not be used and only the library functions can be used. Dee GirlI too do not like the proposed syntax. I'd suggest the following scheme: 1) convert: cast(foo)bar 2) reinterpret: cast!(foo)bar 3) constancy: cast(invariant), cast(mutable) 4) downcast: cast!(foo)barYour proposal is better than mine, but I think I can improve it even more: 1) convert: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)bar 4) downcast: cast(Foo)bar
May 09 2008
On 10/05/2008, Dee Girl <deegirl noreply.com> wrote:With much interest I followed this thread. Forgive my ignorance, but I can not understand one thing. Could this functionality be implemented as library functions all using the built in cast?Not really, since part of the proposal is that const c = new C; auto d = cast(C)c; // ERROR - cast cannot remove constancy should not compile. I see ruling this out as an important part of const correctness. If you make it optional (e.g. have some sort of template, static_cast!(T)), then people are just going to write "cast" instead of "static_cast!" because (a) it's shorter, and (b) the programmer's inherent belief that everything they write is bug free. That's exactly what happens in C++. Ruling out the ability of cast(T) to remove constancy is exactly the same philosophy as that of not allowing const objects to be passed to mutable functions. Naturally, there must be a way of removing constancy if you really, really want to, because D is a systems programming language. That's the reason Yigal suggested cast(mutable), and I suggested cast(!const). Either way, it tells the compiler "I'm doing this on purpose".
May 09 2008
Janice Caron Wrote:On 10/05/2008, Dee Girl <deegirl noreply.com> wrote:But in C++ I can not grep for old-style cast. In D I can (but see my other message...) Another thing I noticed: class C {} void main(string[] args) { auto c = new const C; } Does not compile. But if I change to const(C) it does compile. It is a bit unusual that parens make such a big difference. Thank you, Dee GirlWith much interest I followed this thread. Forgive my ignorance, but I can not understand one thing. Could this functionality be implemented as library functions all using the built in cast?Not really, since part of the proposal is that const c = new C; auto d = cast(C)c; // ERROR - cast cannot remove constancy should not compile. I see ruling this out as an important part of const correctness. If you make it optional (e.g. have some sort of template, static_cast!(T)), then people are just going to write "cast" instead of "static_cast!" because (a) it's shorter, and (b) the programmer's inherent belief that everything they write is bug free. That's exactly what happens in C++.
May 09 2008
On 10/05/2008, Dee Girl <deegirl noreply.com> wrote:But in C++ I can not grep for old-style cast. In D I can.That still doesn't help. You can stare at a bug for ages, and still not see it. But if the compiler tells you it's there, then you can slap your head and say "Doh! Of course! Why didn't I see that!?" The point being that it is way, way, /way/ better to catch bugs at compile-time, than at run-time. Any word for "catching bugs at run time" is "crashing". :-) We had exactly this situation recently with class C {} C c; if (c == null) // now a compile-time error. This was a run-time error for ages. ("c == anything" is translated to "c.opEquals(anything)", which will crash if c is unassigned. But the coder was attempting to test whether or not c was unassigned). Finally it's a compile-time error. Moving the bug detection to compile-time not only saved a lot of people a lot of hair-pulling out, it also exposed many bugs in Phobos. You can grep for "== null", but, so what? People don't. Sometimes its hard to see your own bugs. Having the compiler tell you that they're there is just fantastic. That's the reason for this proposal (the latest version of which is, I think, pretty reasonable). 1) convert or downcast: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)bar
May 09 2008
On Sat, 10 May 2008 07:59:23 +0200, Janice Caron <caron800 googlemail.com> wrote:1) convert or downcast: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)barFor 3), what if I want to cast const(int*) to const(int)*? cast(const(int)*)cast(!const)foo? -- Simen
May 11 2008
On 11/05/2008, Simen Kjaeraas <simen.kjaras gmail.com> wrote:On Sat, 10 May 2008 07:59:23 +0200, Janice Caron <caron800 googlemail.com> wrote:Wow, we're back on topic again! Woo hoo! Good question. My first thought would be const(int*) newPtr = cast(!const)oldPtr; that is, remove all the constancy, then put it back again. Another possibility is: auto newPtr = cast(const(int)*)cast(!const)oldPtr; which is exactly what you suggested. But neither are particularly ideal, so it looks like an ammendment to the proposal is in order. So my new suggestion would be, to remove only /part/ of the constancy, one of the following: cast(!const, T)x cast(T, !const)x Whichever people like best. A third possibility would be to (almost) follow in the footsteps of C++, with const cast(T)x which I kinda like, except that it makes specifying T mandatory, when often you wouldn't need to.1) convert or downcast: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)barFor 3), what if I want to cast const(int*) to const(int)*? cast(const(int)*)cast(!const)foo?
May 11 2008
Janice Caron wrote:On 11/05/2008, Simen Kjaeraas <simen.kjaras gmail.com> wrote:another option would be to allow: cast(const(T))x; where you redefine the constancy of x and this would be subject to a test that T is not a different type (except for constancy, of course). that could simply be a syntax sugar for two separate semantic casts (even if the compiler does it in one operation) so, this would become: const(int*) x = ...; auto newX = cast(const(int)*)x; the check is to take both types, remove all const/invariant and compare. the result must be identical. thus, const(int*) => (int*) and const(int)* => (int)* which are identical. put a different amount of stars, or replace int with long and the cast fails. I'm not sure if this is that important to add, since you can just use two casts. sprinkle this post with invariant where needed, to make this a full proposal. --YigalOn Sat, 10 May 2008 07:59:23 +0200, Janice Caron <caron800 googlemail.com> wrote:Wow, we're back on topic again! Woo hoo! Good question. My first thought would be const(int*) newPtr = cast(!const)oldPtr; that is, remove all the constancy, then put it back again. Another possibility is: auto newPtr = cast(const(int)*)cast(!const)oldPtr; which is exactly what you suggested. But neither are particularly ideal, so it looks like an ammendment to the proposal is in order. So my new suggestion would be, to remove only /part/ of the constancy, one of the following: cast(!const, T)x cast(T, !const)x Whichever people like best. A third possibility would be to (almost) follow in the footsteps of C++, with const cast(T)x which I kinda like, except that it makes specifying T mandatory, when often you wouldn't need to.1) convert or downcast: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)barFor 3), what if I want to cast const(int*) to const(int)*? cast(const(int)*)cast(!const)foo?
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:another option would be to allow: cast(const(T))x; where you redefine the constancy of x and this would be subject to a test that T is not a different type (except for constancy, of course).Oh, I see. So, cast(T) would double up for /either/ downcasting, /or/ removing constancy, but never both at the same time. (Both at the same time would require two casts). This is probably the best suggestion of all. I like it.
May 11 2008
Janice Caron Wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:Actually, we got it slightly wrong: since D defines removal of invariance as undefined behavior and since casting with constancy does not convert the value but rather changes the type tag of the value, it would be more appropriate to use the "cast!" form, IMO. it feels more appropriate to me cause it warns the user that it's his responsibility if something breaks. That's the entire reason for the const! form, isn't it? --Yigalanother option would be to allow: cast(const(T))x; where you redefine the constancy of x and this would be subject to a test that T is not a different type (except for constancy, of course).Oh, I see. So, cast(T) would double up for /either/ downcasting, /or/ removing constancy, but never both at the same time. (Both at the same time would require two casts). This is probably the best suggestion of all. I like it.
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:Actually, we got it slightly wrong: since D defines removal of invariance as undefined behavior and since casting with constancy does not convert the value but rather changes the type tag of the value, it would be more appropriate to use the "cast!" form, IMO.Hmmm. Perhaps. But then you lose the distinction between const_cast and reinterpret_cast. With cast!() acting as reinterpret_cast, I can do: void foo(Something x) { auto y = cast!(Anything)x; ... } and it would always succeed, and you can't say "unless there's a change of constancy" in this case, because the before and after might have completely different memory layouts, so there'd be no way even to tell whether or not that had happened. I can see that const_cast and dynamic_cast could share the same syntax, but I really don't see how reinterpret_cast could double up with /anything/ else. So maybe that brings us to this: cast(T)x // dynamic_cast cast!(T)x // reinterpret_cast static cast(T)x // static_cast const cast(T)x // const_cast That does have a certain appeal to it.
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I'm not sure your syntax can be used since those two words already have a different meaning, wouldn't it create ambiguity in the language? --Yigal since i don't classify the casts as you do, I'm not sure I agree with the above. didn't you yourself complain that cast(T) can remove constancy by mistake and create bugs? why would you allow cast!(T) to do just that? anyway, I need to think more about that. I haven't arrived at a conclusion yet, so I'd appreciate more examples/explanations. Also, My definition was that cast() can change the memory while cast!() doesn't. it seems you define them in reverse of that. --YigalActually, we got it slightly wrong: since D defines removal of invariance as undefined behavior and since casting with constancy does not convert the value but rather changes the type tag of the value, it would be more appropriate to use the "cast!" form, IMO.Hmmm. Perhaps. But then you lose the distinction between const_cast and reinterpret_cast. With cast!() acting as reinterpret_cast, I can do: void foo(Something x) { auto y = cast!(Anything)x; ... } and it would always succeed, and you can't say "unless there's a change of constancy" in this case, because the before and after might have completely different memory layouts, so there'd be no way even to tell whether or not that had happened. I can see that const_cast and dynamic_cast could share the same syntax, but I really don't see how reinterpret_cast could double up with /anything/ else. So maybe that brings us to this: cast(T)x // dynamic_cast cast!(T)x // reinterpret_cast static cast(T)x // static_cast const cast(T)x // const_cast That does have a certain appeal to it.
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:> So maybe that brings us to this: > > cast(T)x // dynamic_cast > cast!(T)x // reinterpret_cast > static cast(T)x // static_cast > const cast(T)x // const_cast > > That does have a certain appeal to it. didn't you yourself complain that cast(T) can remove constancy by mistake and create bugs?Yes, which is why I now suggest void foo(in C c) { C c = cast(C)c; /* ERROR - cast cannot remove constancy */ C d = const cast(C)c /* OK */ }why would you allow cast!(T) to do just that?As in... void foo(in C c) { C c = cast!(C)c; /* OK */ } Because the exclamation mark means "allow anything" (but you're only ever reinterpreting bits, not assigning new ones). Reinterpret cast would most often be used for casting void* to T*, for some T, but it could just as easily be used to interpret T* as U*, or (for classes) C as D. Since the source type may have a completely different memory layout from the destination type, there is, in general, no way for the compiler to determine whether or not constancy has changed. (e.g. you could be casting from a pointer to an intptr_t, or vice versa). In short, this is the most dangerous form of cast of all, and should only ever be used when nothing else will work, and even then only if you're damn sure you need to circumvent the normal rules. (But since D is a systems programming language, this kind of super-dangerous casting must be allowed. It just shouldn't be the default. To my mind, the exclamation mark says that). Bear in mind that even without reinterpret cast, you will always be able to achieve the same thing with unions. Watch: DstType reinterpret_cast(DstType, SrcType)(SrcType x) { union U { SrcType src; DstType dst; } U u; u.src = x; return u.dst; } In other words, if you ban reinterpret cast, someone's only going to do it anyway. Ummm ... wait a minute ... ! I think I just found a reason /not/ to have a reinterpret cast in the language - the reason being, it can be done in a library function, as I just showed. Hey, I think that means we don't need that one at all. Hmm...Also, My definition was that cast() can change the memory while cast!() doesn't. it seems you define them in reverse of that.Well, not quite. In C++, only static_cast<T>(x) can change the memory (but it might not), wheras I suggest that in D, cast(T)x (i.e. the default cast) should just "do the sensible thing" - sort of like it does now, except with the dangerous cases removed - so it would act like dynamic_cast<T>(x) where appropriate, and static_cast<T>(x) where dynamic_cast<T> would be inappropriate. So, sometimes it will change memory, sometimes not. But const cast and cast! would never change memory. Examples: double d; int x = cast(int)d; // creates new int class A {} class B : A {} A a = new B; B b = cast(B)a; // dynamic cast. a and b point to same object C b = static cast(B)a; // as above, but without the runtime check that a is really a B class D {} const d = new D; D d2 = cast(D)d; // ERROR - cannot remove constancy D d2 = const cast(D)d; // OK D d3 = cast(D)a; // ERROR - can't convert A to D D d3 = static cast(D)a; // ERROR - can't convert A to D D d3 = const cast(D)a; // ERROR - can't convert A to D D d3 = cast!(D)a; // OK - but you'd better know what you're doing!
May 11 2008
Janice Caron wrote: <snip> if interpret_cast can be implemented by the user and it is quite dangerous, than i think people who need that should implement that themselves with union as you showed. no need for D to provide that functionality by default. I'm still not sure about constancy casts. but in order to provide the minimal solution, I'd prefer cast!(T) to do constancy casts over adding another construct for that. also, I don't like the static cast(), and it might cause ambiguity problems. what is this good for? open question: if this proposal does not provide a reinterpret_cast, can we split the cast!(T) to perform either a type change /or/ a constancy change? int n = ...; auto d1 = cast(double)n; //converts the n to double so 1 => 1.0 // below case uses the same bit pattern, exception on invalid double // value auto d2 = cast!(double)n; same goes for classes with user defined cast and cast! operators. downcast can be treated as a compiler pre-defined conversion from a class to its derived classes. uses the cast(Derived) form and throws exception on error. constancy is performed via cast!(T). pointers are cast with cast!(T) so to convert const(int)* to const(long*) you'll need something like: auto res = cast!(const(long*))cast(const(long)*)x;
May 11 2008
On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:if [re]interpret_cast can be implemented by the user and it is quite dangerous, than i think people who need that should implement that themselves with union as you showed. no need for D to provide that functionality by default.Agreed. As a general principle, if it can be implemented in a library, then there's no need for new syntax. /However/ - it /should/ be implemented in a library, because we'd want to outlaw void* p; char* q = cast(char*) p; /* ERROR -- cannot cast from void* to char* */ reinterpret_cast should be the only kind of cast to allow this: char* q = reinterpret_cast!(char*)(p); /* OK */ [EDIT - see below]I'm still not sure about constancy casts. but in order to provide the minimal solution, I'd prefer cast!(T) to do constancy casts over adding another construct for that.OK.also, I don't like the static cast(), and it might cause ambiguity problems. what is this good for?There can't be any ambiguity problems (yet), because you can't (yet) call functions which take a "this" parameter at compile time. If you could, then "static cast(int)x", might invoke typeof(x)'s opCast() function, so arguably there might be ambiguity problems in the future -- if you're entering an obfuscated D contest! Let's ignore that for now. What's it good for? Performance. If B is a subclass of A, and I *know* that my A is actualy a B, static_cast<B> allows me avoid the runtime penalty that I'd get with dynamic_cast<B>. There would also be no need to check the return value, because static_cast always succeeds. Obviously, reinterpret_cast<B> will work everywhere that static_cast<B> works, but static_cast<B>(a) gets me a compile-time check that typeof(a) is a subclass or superclass of B, wheras with reinterpret_cast<B>(a) there's no compile-time checking at all. static_cast<T> can also do plain conversion, e.g. double d; int n = static_cast<int>(d); So, if the old-fashioned default cast were ever outlawed in C++, static_cast is the one you'd use to replace it for "plain" casts. However - given that we choose to define cast(T) to mean "if T is a class then use RTTI, otherwise don't", then, for non-classes, our cast(T) achieves what static_cast<T> does for C++. For classes ... well, who needs static cast anyway? Let's ditch it. So that leaves us with: cast(T)x // (T is a class) ? dynamic_cast : static_cast cast!(T)x // const_cast reinterpret_cast!(T)(x) // reinterpret_cast, implemented in library That pretty much covers it.open question: if this proposal does not provide a reinterpret_cast, can we split the cast!(T) to perform either a type change /or/ a constancy change?I don't see how that could work in general. We could allow it for specific cases though - e.g casting from void* to T*, or from void[] to T[], etc. I think in those special cases, it could work. So, to turn an S* into a T* (where both are structs), you would do S* s = cast!(S*)cast(void*)t// below case uses the same bit pattern, exception on invalid double // value auto d2 = cast!(double)n;I don't like that, and I see no need for it. reinterpret_cast!(double) can do that job. And there's no need for exception throwing - that would entail a runtime check, and at this level, we must assume the programmer knows what they're doing.same goes for classes with user defined cast and cast! operators.There should never be any such thing as a user-defined cast! operator. It's meaningless. Look at it like this - C++ has four different kinds of cast, but only one kind of cast operator. It's all you need.downcast can be treated as a compiler pre-defined conversion from a class to its derived classes. uses the cast(Derived) form and throws exception on error.It's not an error to fail an RTTI cast! e.g. void f(A a) { B1 b1 = cast(B1)a; if (b1 !is null) { ... } B2 b2 = cast(B2)a; if (b2 !is null) { ... } B3 b3 = cast(B3)a; if (b3 !is null) { ... } } So no need for throwing exceptions.constancy is performed via cast!(T).OK.pointers are cast with cast!(T)Only pointers to void.so to convert const(int)* to const(long*) you'll need something like: auto res = cast!(const(long*))cast(const(long)*)x;I would have said cast!(const(long*))cast(const(void)*)x; or reinterpret_cast!(const(long*))x;
May 11 2008
Janice Caron wrote: <snip> I agree on mostly everything, I'm still not convinced about the rtti cast not throwing on error, though. I'll just add that cast! won't be used to generally change types but since we added _specific_ cases that cast! handles, it's worth considering adding another special case which is the static_cast for classes.
May 11 2008
Simen Kjaeraas wrote:For 3), what if I want to cast const(int*) to const(int)*? cast(const(int)*)cast(!const)foo? -- Simenthat looks correct.
May 11 2008
"Simen Kjaeraas" wroteOn Sat, 10 May 2008 07:59:23 +0200, Janice Caron wrote:I'm not really joining this discussion (yet), but I just wanted to respond to this. If the purpose of your cast is to assign to a const(int) * variable, you need no casts to do this. const(int*) implicitly casts to const(int)*: int m = 5; const(int *) ptr = &m; const(int)* ptr2 = ptr; // OK Why? Because I can't change anything through ptr2 that I couldn't change through ptr. Now, if you casting to be able to rebind ptr, then I think you would need some crazy casting like you stated. -Steve1) convert or downcast: cast(Foo)bar 2) reinterpret: cast!(Foo)bar 3) constancy: cast(invariant)bar, cast(!const)barFor 3), what if I want to cast const(int*) to const(int)*? cast(const(int)*)cast(!const)foo? -- Simen
May 12 2008
Steven Schveighoffer <schveiguy yahoo.com> wrote:Now, if you casting to be able to rebind ptr, then I think you would need some crazy casting like you stated. -SteveActually, I had no other intent than to see if I could break the system. For a better (or worse, mayhaps) version: const(C*)* (or whatever, really) to const(invariant(C)*)*. For this, I'd prefer Janice's suggestion of const cast, as I understand it: const(C*)* foo; cast(const(invariant(C)*)*)foo; // error, cast can't change constancy const cast(const(invariant(C)*)*)foo; // works like a charm The const cast looks good to me, whereas what I used in the last example was downright ugly. Casting to a different constancy should be one cast, not first making it all mutable, and then put const back in there. -- Simen
May 12 2008
Simen Kjaeraas wrote:Steven Schveighoffer <schveiguy yahoo.com> wrote:if I'm not mistaken, the other example was both a cast of constancy and of type. that needs two casts (that's one of the main principles of this proposal). in the latest version of this proposal, the above would be: const(C*)* foo; cast!(const(invariant(C)*)*)foo; // works like a charm a cast without the ! is not defined for constancy casts, so the compiler would err and suggest using cast! instead (maybe with a message than warn the user that this can be dangerous)Now, if you casting to be able to rebind ptr, then I think you would need some crazy casting like you stated. -SteveActually, I had no other intent than to see if I could break the system. For a better (or worse, mayhaps) version: const(C*)* (or whatever, really) to const(invariant(C)*)*. For this, I'd prefer Janice's suggestion of const cast, as I understand it: const(C*)* foo; cast(const(invariant(C)*)*)foo; // error, cast can't change constancy const cast(const(invariant(C)*)*)foo; // works like a charm The const cast looks good to me, whereas what I used in the last example was downright ugly. Casting to a different constancy should be one cast, not first making it all mutable, and then put const back in there. -- Simen
May 12 2008
thanks for the added examples and explanations to the proposal. I want to note two things though: a) I did suggest cast(!invariant) instead of cast(mutable) but I'm not sure it's more readable. maybe cast(!const) is a bit better (and shorter) but the lack of symmetry bothers me. b) Downcasts - the danger with them is that they can fail if you downcast to the wrong derived class. that is why I've suggested: class B : A; class C : A; A b = new B; C c1 = cast!(C)b; C c2 = cast(C)b; the first cast will fail and return a null. the exclamation mark should remind the programmer that this can happen and therefore it's his responsibility to check the returned value of the cast. This is current D behavior. OTOH, the second cast is a "safe" cast and must always work, therefore the failure to cast will result in an exception being thrown. another thing: the second cast does not have to fail if the user defined his own cast from B to C. "cast" can be defined by the user, but I'm not sure whether it would be a good idea to allow the same for "cast!" what do you think? --Yigal
May 09 2008
On 10/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:b) Downcasts - the danger with them is that they can fail if you downcast to the wrong derived class. that is why I've suggested: <snip>Right. But in my view cast! (with an exclamation mark) can never fail as such, because it's the programmer telling the compiler, "Yes, I know you think this is wrong, but dammit I know what I'm doing, so deal with it!". It means that the compiler makes no checks, and trusts the programmer. (It's reinterpret_cast<T>). Therefore, it can never return null. But you are certainly correct in that there is a fundamental difference between static_cast<T> and dynamic_cast<T>. The former is a compile-time check, and the latter is a run-time check, so when the user types cast(T)x; the compiler does a compile-time check to determine whether the static type, typeof(x), could be downcast to T, and if so, generates code which performs a runtime check, which might return null. Although this works, the user is never entirely sure (a) whether the compiler has generated a straight conversion which will always succeed, or (b) whether the compiler has generated a runtime check, which is slower, and might fail. For that reason, there is merit in wanting an explicit dynamic_cast<T> equivalent. But it can't be cast!(T), because we've already used that for reinterpret_cast<T> - a cast which performs no checks at all, not even at compile time. Maybe we need a different syntax still for dynamic_cast<T>. One possibility (thought I don't recommend it) is: cast(class T)x The problem with that is that all the existing code which expects cast(T)x to be a dynamic cast will still compile, give the wrong result, and never return null. The change would introduce bugs, which is the exact opposite of the intent. For that reason, perhaps dynamic cast should be the default (so that existing code will still compile and give the /right/ result), with static_cast<T> having a different syntax. One really, really obvious syntax for static_cast<T>(x) is static cast(T)x which I think is pretty cool. It tells the compiler "I definitely want to bypass the run-time check, but I still want the compile-time check"."cast" can be defined by the user, but I'm not sure whether it would be a good idea to allow the same for "cast!" what do you think?I think that cast! should mean reinterpret_cast<T>, and therefore always succeeds, bypassing all checks. Since it merely reinterprets the bits, it cannot be overloadable.
May 10 2008
I do not agree with the premise that a reinterpret cast always succeeds, even the C++ version can throw an exception! maybe, my use of the word "fail" was not accurate. let me re-phrase my thoughts: there are only two ways to coerce one type into another: a) you convert the value (i.e you change the underlying memory) - this should always produce the expected result [I mark this with cast(T)] b) you do _not_ convert the value, you just change the type tag attached to the value - this can produce bad results which do not conform to the underlying memory scheme of the type. [I mark this with cast!(T)] let's look at an example of the latter: int a = ...; auto c = cast!(dchar) a; suppose the bit layout of a is illegal utf-32 encoding. would you prefer D allowed storing such an illegal value in a dchar? IMO, a strongly typed language (like D) must enforce at all times that its variables are valid. I do not want D to allow storing illegal values like that. that must be an error. Am I making any sense? --Yigal
May 10 2008
On 10/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:suppose the bit layout of a is illegal utf-32 encoding. would you prefer D allowed storing such an illegal value in a dchar?I have to say yes. It's a question of levels. Higher level code should never store invalid UTF-32 in dchars nor dstrings. But lower level code must be able to work with them. For example, I wrote std.encoding. It has a function isValidCodePoint() which takes a dchar and tells you whether or not it contains a valid value. It also has a function, sanitize(), which takes possibly invalid UTF as input, and emits guaranteed valid UTF as output. It has a function, safeDecode(), which takes possibly invalid UTF as input, removes the first UTF sequence, regardless of whether valid or malformed, and returns either the decoded character or the constant INVALID_SEQUENCE, which is (cast(dchar)(-1)). The lowest level code has to be allowed, not merely /close/ to the metal, but to actually turn the nuts and bolts. That's what it means to be a systems programming language, and without that ability, no low level code could ever be written, without resorting to assembler. So yes, int n = anything; dchar c = cast!(dchar)n; must always succeed. The exclamation mark means "I know what I'm doing", which is exactly why it should be used with caution.IMO, a strongly typed language (like D) must enforce at all times that its variables are valid. I do not want D to allow storing illegal values like that. that must be an error.Consider this: string s = "\u20AC"; /* s contains exactly one Unicode character */ string t = s[1..2]; Do you want to ban slicing? Do you want slicing always to invoke a call to std.encoding.isValid(), just to make sure the slice is valid? If so, you must see that std.encoding itself needs to be allowed to do low-level stuff. Higher level code is ultimately written in terms of lower level code, so you can't ban the lower level code. However, I would be more than happy with; int n; dchar c = cast(dchar)n; /* may throw */ dchar d = cast!(dchar)n; /* always succeeds */
May 10 2008
Janice Caron Wrote:Consider this: string s = "\u20AC"; /* s contains exactly one Unicode character */ string t = s[1..2];this makes string array of bytes rather than chars.
May 10 2008
On 10/05/2008, terranium <spam here.lot> wrote:Janice Caron Wrote: > Consider this: > > string s = "\u20AC"; /* s contains exactly one Unicode character */ > string t = s[1..2]; this makes string array of bytes rather than chars.You are incorrect. Indisputably, typeof(t) == invariant(char)[]. It is an array of invariant chars - that is, an array of invariant UTF-8 code units. Each code unit is individually valid, but the complete string consists of two malformed code unit sequences, each of which is an isolated continuation byte. You are also missing the point. This thread is about casting, not Unicode. If you want to talk Unicode, I'm happy to do so, but please let's take that to another thread. I only brought up slicing as an example of why low level stuff must be permitted, and in specific response to a point made by Yigal.
May 10 2008
Janice Caron Wrote:If you want to talk UnicodeI'll let others to have the pleasure to discover this themselves :)))
May 10 2008
On 10/05/2008, Janice Caron <caron800 googlemail.com> wrote:You are incorrect. Indisputably, typeof(t) == invariant(char)[]. It is an array of invariant chars - that is, an array of invariant UTF-8 code units. Each code unit is individually valid, but the complete string consists of two malformed code unit sequences, each of which is an isolated continuation byte.My apologies. The complete string consists of /one/ malformed code unit sequence, consisting of one isolated continuation byte, not two. For some reason I misread s[1..2] as s[1..3] (which is pretty dumb really, seeing as I wrote it). :-)If you want to talk Unicode, I'm happy to do so, but please let's take that to another thread.This still stands.
May 10 2008
IMHO, your reply makes perfect sense for C/C++ but not for D. specifically because D has other facilities to handle those cases. a dchar (or [w]char) _must_ always contain valid data. if you need to store other encodings you can use ubyte instead which does not limit you to a specific bit pattern (this is why D has it in the first place...) the above example of slices can be easily dealt with since (unlike in C/C++) D arrays know their length. this is similar to the fact that D checks bounds on arrays and throws on error (in debug mode) whereas C/C++ does not. IMO, the D implementation itself (both the compiler and the runtime) need to make sure chars are always valid. this should not be something optional added via a library. I agree with you notion of levels, I just think D provides for much better facilities for low-level coding compared to using unsafe C/C++ conventions. int n; dchar c = cast(dchar)n; dchar d = cast!(dchar)n; in the above code, the second one should be used and it might throw. the first simply does not make any sense and should produce a compiler error because you cannot convert an int value to a dchar (unless it's a one digit int) <off topic rant> What worries me most about D is the fact that D becomes an extension to C++. The whole idea behind D was to create a new language without all the baggage and backward compatibility issues of C++. I don't want a slightly more readable version of C++ since I'll get that with C++0x. c++ programmers want D to have a D "stl" and a D boost. that's wrong! STL is badly designed and employs massive amounts of black magic that ordinary people do not understand. (I suffer at work while writing in C++). in what world does it make sense to mark an abstract method with "=0;" at the end, especially when the method is horizontally long and that gets to be off screen! D should be written with a D mindset which should be the best ingredients extracted from all those languages D got its influences designing such a new D mindset, IMO. Phobos is not, since it's merely C code written with D syntax, with all those new shiny code Andrei added which is C++ code written with D syntax. I appreciate his great expertise in C++, but I already can use C++ libraries in C++ without learning a new language. D needs to be better. *much* better. </rant> --Yigal
May 10 2008
Yigal Chripun Wrote:<off topic rant> What worries me most about D is the fact that D becomes an extension to C++. The whole idea behind D was to create a new language without all the baggage and backward compatibility issues of C++. I don't want a slightly more readable version of C++ since I'll get that with C++0x. c++ programmers want D to have a D "stl" and a D boost. that's wrong! STL is badly designed and employs massive amounts of black magic that ordinary people do not understand. (I suffer at work while writing in C++). in what world does it make sense to mark an abstract method with "=0;" at the end, especially when the method is horizontally long and that gets to be off screen! D should be written with a D mindset which should be the best ingredients extracted from all those languages D got its influences designing such a new D mindset, IMO. Phobos is not, since it's merely C code written with D syntax, with all those new shiny code Andrei added which is C++ code written with D syntax. I appreciate his great expertise in C++, but I already can use C++ libraries in C++ without learning a new language. D needs to be better. *much* better. </rant>I want to understand what you said because it can change my decision to use D1 or D2 or not at all. From what I read in Phobos some good examples of interesting D mindset that are hard in C++ or other languages are std.typecons and std.algorithm. There are parts of Phobos that look very ugly. For example the streams. I used STL and it is useful to me. If it is true for many people then why D not take the good parts of it? If you know of bad designs in STL they could be avoided in D. Also STL has many balck magic but it is because of C++ imperfections. The definition of STL is mathematic very clean. Why do you mention the =0 sintax? D does not have it. And I do not think this problem is related to STL. What are good examples that show Tango is a good example of designing with a new D mindset? Thank you. Dee Girl
May 10 2008
== Quote from Dee Girl (deegirl noreply.com)'s articleWhat are good examples that show Tango is a good example of designing with a new D mindset?I'm somewhat biased since I created the module, but I'd consider tango.core.Array to be a good example of a D-oriented mindset. It's an array-specific algorithm module intended to leverage the D slice syntax for range speficication. For exmaple: import tango.core.Array; import tango.stdc.stdio; void main() { int[] buf = [1,6,2,5,9,2,3,2,4].dup; // calls Array.sort with optional predicate buf[0 .. 3].sort( (int x, int y) { reuturn x < y; } ); assert( buf[0 .. 3] == [1,2,5,6]); buf.sort(); // full sort of buf with default predicate // below is equivalent to equal_range in C++ printf( "there are %d 2s in buf\n", buf[buf.lbound(2) .. buf.ubound(2)].length ); // more fun stuff printf( "there are %d 5s between index 2 and 6\n", buf[2 .. 6].count( 5 ) ); } etc. (I'm using printf for the sake of illustration, not because I suggest you actually use it in your app) Sean
May 10 2008
Sean Kelly Wrote:== Quote from Dee Girl (deegirl noreply.com)'s articleNice example! How did you do it? Did Tango change the compiler and added more methods to arrays? Thank you, Dee GirlWhat are good examples that show Tango is a good example of designing with a new D mindset?I'm somewhat biased since I created the module, but I'd consider tango.core.Array to be a good example of a D-oriented mindset. It's an array-specific algorithm module intended to leverage the D slice syntax for range speficication. For exmaple: import tango.core.Array; import tango.stdc.stdio; void main() { int[] buf = [1,6,2,5,9,2,3,2,4].dup; // calls Array.sort with optional predicate buf[0 .. 3].sort( (int x, int y) { reuturn x < y; } ); assert( buf[0 .. 3] == [1,2,5,6]); buf.sort(); // full sort of buf with default predicate // below is equivalent to equal_range in C++ printf( "there are %d 2s in buf\n", buf[buf.lbound(2) .. buf.ubound(2)].length ); // more fun stuff printf( "there are %d 5s between index 2 and 6\n", buf[2 .. 6].count( 5 ) ); } etc. (I'm using printf for the sake of illustration, not because I suggest you actually use it in your app) Sean
May 10 2008
On 10/05/2008, Dee Girl <deegirl noreply.com> wrote:Nice example! How did you do it? Did Tango change the compiler and added more methods to arrays? Thank you, Dee GirlYeah, but look what you can do with Phobos. import std.algorithm; int[] array = [ 1, 2, 3, 4 ]; // sort in descending order sort!("a > b")(array); assert(array == [ 4, 3, 2, 1 ]); // sort in ascending order sort(array); assert(array == [ 1, 2, 3, 4 ]); // sort with a delegate bool myComp(int x, int y) { return x > y; } sort!(myComp)(array); assert(array == [ 4, 3, 2, 1 ]); // Showcase stable sorting string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; sort!("toupper(a) < toupper(b)", SwapStrategy.stable)(words); assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]);
May 10 2008
Janice Caron wrote:On 10/05/2008, Dee Girl <deegirl noreply.com> wrote:here comes my personal emotional response: I hate the above code! it smells to much like C++. I really dislike the use of templates here, which is unnecessary, and even more so the use of those strings. a Much better way would be to use: sort(array, (int a, int b){ return x > y; }); I wish D would add the syntax sugar proposed by downs so that could be written as: sort(array, (int a, int b)) { return x > y; } also, you can rewrite it as: array.sort(...); which is even better IMO. -- YigalNice example! How did you do it? Did Tango change the compiler and added more methods to arrays? Thank you, Dee GirlYeah, but look what you can do with Phobos. import std.algorithm; int[] array = [ 1, 2, 3, 4 ]; // sort in descending order sort!("a > b")(array); assert(array == [ 4, 3, 2, 1 ]); // sort in ascending order sort(array); assert(array == [ 1, 2, 3, 4 ]); // sort with a delegate bool myComp(int x, int y) { return x > y; } sort!(myComp)(array); assert(array == [ 4, 3, 2, 1 ]); // Showcase stable sorting string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; sort!("toupper(a) < toupper(b)", SwapStrategy.stable)(words); assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]);
May 10 2008
Yigal Chripun Wrote:Janice Caron wrote:I agree! Strings and aliases are much less powerful than delegates and closures because they are static and can not have state. But I am confused about something. I compiled this with phobos: sort!(function bool(int a, int b) { return a > b; })(array); There is clear difference in syntax from your examples. But my question is: what are the differences in semantics? Thank you, Dee GirlOn 10/05/2008, Dee Girl <deegirl noreply.com> wrote:here comes my personal emotional response: I hate the above code! it smells to much like C++. I really dislike the use of templates here, which is unnecessary, and even more so the use of those strings. a Much better way would be to use: sort(array, (int a, int b){ return x > y; }); I wish D would add the syntax sugar proposed by downs so that could be written as: sort(array, (int a, int b)) { return x > y; } also, you can rewrite it as: array.sort(...); which is even better IMO.Nice example! How did you do it? Did Tango change the compiler and added more methods to arrays? Thank you, Dee GirlYeah, but look what you can do with Phobos. import std.algorithm; int[] array = [ 1, 2, 3, 4 ]; // sort in descending order sort!("a > b")(array); assert(array == [ 4, 3, 2, 1 ]); // sort in ascending order sort(array); assert(array == [ 1, 2, 3, 4 ]); // sort with a delegate bool myComp(int x, int y) { return x > y; } sort!(myComp)(array); assert(array == [ 4, 3, 2, 1 ]); // Showcase stable sorting string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; sort!("toupper(a) < toupper(b)", SwapStrategy.stable)(words); assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]);
May 10 2008
Dee Girl wrote:I agree! Strings and aliases are much less powerful than delegates and closures because they are static and can not have state. But I am confused about something. I compiled this with phobos: sort!(function bool(int a, int b) { return a > b; })(array); There is clear difference in syntax from your examples. But my question is: what are the differences in semantics? Thank you, Dee GirlI'm not sure what exactly are you asking. are you asking what's the difference between a delegate and a function? --Yigal
May 10 2008
Yigal Chripun Wrote:Dee Girl wrote:Please let me rephrase. What is the difference between a function passed as an alias, and a delegate? You hate one and you like the other so the difference must be big. But I do not find it. Thank you, Dee GirlI agree! Strings and aliases are much less powerful than delegates and closures because they are static and can not have state. But I am confused about something. I compiled this with phobos: sort!(function bool(int a, int b) { return a > b; })(array); There is clear difference in syntax from your examples. But my question is: what are the differences in semantics? Thank you, Dee GirlI'm not sure what exactly are you asking. are you asking what's the difference between a delegate and a function?
May 10 2008
Reply to Dee,Yigal Chripun Wrote:a function is just a free function or a static class function or the like. A delegate is both a function and a context. Generally the context is another function's scope or an object or struct.Dee Girl wrote:Please let me rephrase. What is the difference between a function passed as an alias, and a delegate? You hate one and you like the other so the difference must be big. But I do not find it. Thank you, Dee GirlI agree! Strings and aliases are much less powerful than delegates and closures because they are static and can not have state. But I am confused about something. I compiled this with phobos: sort!(function bool(int a, int b) { return a > b; })(array); There is clear difference in syntax from your examples. But my question is: what are the differences in semantics? Thank you, Dee GirlI'm not sure what exactly are you asking. are you asking what's the difference between a delegate and a function?
May 10 2008
BCS Wrote:Reply to Dee,Sorry I do not know how to express myself. It is frustrating ^_^. I understand what a function is. But it looks to me as when a function is passed to a template weird things happen. I have compiled and run successfully this program: import std.stdio; import std.algorithm; void main() { int[] array = [ 1, 2, 3, 4 ]; int x = 5; bool comp(int a, int b) { return a + x > b + x; } sort!(comp)(array); writeln(array); sort(array); writeln(array); test(&comp); writeln(array); } void test(bool delegate(int, int) dg) { int[] array = [ 1, 2, 3, 4 ]; sort!(dg)(array); } There are many interesting things about above program. Function comp accesses a variable in its enclosing scope therefore it can not be a "simple" function. However sort works with it. Then even more interesting. I pass the function as a delegate to another function. Here sort works again even when passed the delegate! So I see no difference between passing the delegate as a template alias or as a normal parameter. But I do not understand how the compiler can generate the code for the dynamic case. Dee GirlYigal Chripun Wrote:a function is just a free function or a static class function or the like. A delegate is both a function and a context. Generally the context is another function's scope or an object or struct.Dee Girl wrote:Please let me rephrase. What is the difference between a function passed as an alias, and a delegate? You hate one and you like the other so the difference must be big. But I do not find it. Thank you, Dee GirlI agree! Strings and aliases are much less powerful than delegates and closures because they are static and can not have state. But I am confused about something. I compiled this with phobos: sort!(function bool(int a, int b) { return a > b; })(array); There is clear difference in syntax from your examples. But my question is: what are the differences in semantics? Thank you, Dee GirlI'm not sure what exactly are you asking. are you asking what's the difference between a delegate and a function?
May 10 2008
Your example does use delegates. the difference is this: say you have two delegates you pass to sort, for example you sort one array in ascending order, and another in descending order. the template solution will generate two almost identical sort functions with the only difference that one sort instance calls internally the first delegate, and the second instance calls the second. you get 4 functions. without the template, you get one sort function and two delegates, so only 3 functions. Now, when you use a string with the template, the sort will contain the code itself inline to the sort instance so it "saves" you a function call. IMO, the string solution is not clean ( C MACROs? )and while for simple cases it could save you a function call it's premature optimization IMO. Also, if I use delegates, I can't see how a template can save that function call. so it just bloats my executable with unneeded copies of sort. I hate when library code forces me to this. I think the programmer knows his code better than the library writer and should be able to choose himself whether he needs to use a compile time solution to save function calls or a run-time solution to prevent unnecessary executable bloat. this optimization should be made by the programmer and _not_ the library writer. This solution does not give me that freedom. this solution is a classic STL piece of code, BUT, the main difference is that for C++ this is probably the only reasonable solution as C++ doesn't have delegates. this is why when coding for D I urge people to rethink their solution instead of just doing what is best in C++ (or any other preferred language of the library writer). This is a prime example why the C++ solution is wrong for D. --Yigal
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:BUT, the main difference is that for C++ this is probably the only reasonable solution as C++ doesn't have delegates.C++ has functors (classes which overload operator ()), and they're a bit like delgates. What C++ doesn't have is alias template parameters, nor string template parameters. sort!("a.member > b.member")(array) simply couldn't be done in C++. This is /not/ a C++ solution. It's a D solution, and a fantastically brilliant one. Perhaps what you mean when you say "C++ solution", is, "it uses templates"?
May 11 2008
On Sun, 11 May 2008 13:18:33 +0400, Janice Caron <caron800 googlemail.com> wrote:sort!("a.member > b.member")(array)I like this one, too. It's very simple, efficent and clean.
May 11 2008
Koroskin Denis wrote:On Sun, 11 May 2008 13:18:33 +0400, Janice Caron <caron800 googlemail.com> wrote:In what way is that clean? How is that different from a C macro? if you have a typo: "a.member > b.membr" the compiler won't check it at the place you wrote it, since it's just a string. the compiler will probably complain about the library code instead. (I haven't tried that on actual code, though). Now there is a way to tell the compiler to validate the content of strings with q"here goes a valid D code snippet" (if i remember correctly), but that requires me to not forget that q. all in all, all those string manipulations, although powerful, are quite dangerous, IMO. also, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere. --Yigalsort!("a.member > b.member")(array)I like this one, too. It's very simple, efficent and clean.
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:also, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere.On that point, I concede. The reporting of template errors could certainly be improved. More than once I have wanted to see the "stack trace" of errors, working back from the innermost, to the line of code that ultimately triggered it. However, that it not an argument against templates, it is an argument for improved error reporting. And hopefully, one day we'll get that. This is not the first time that you've argued that some feature or strategy is bad because today's D compiler isn't good enough, but you need to remember that tomorrow's D compiler will be better. That's life on the cutting edge.
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:are we now debating the "sufficiently smart compiler" problem? ;) I'm just kidding. But seriously, That is just another symptom of the issue. the main thing is that the STL design of a collection of generic templates is wrong, for all the reasons I've stated. C++ experts have this mindset of all we got is a hammer(templates), so all the problems are nails" which is simply not true. templates are a useful feature which unfortunately is greatly overused. The same thing can be said above Java and configuration files in XML. Java programmer sometimes you some Java code to glue their XML configuration files. Again, nothing's wrong with xml itself. XML is a grea thing when used properly, the issue is that not all problems can be solved with XML configuration files. Maybe your rich C++ experience affects your judgment regarding this? --Yigalalso, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere.On that point, I concede. The reporting of template errors could certainly be improved. More than once I have wanted to see the "stack trace" of errors, working back from the innermost, to the line of code that ultimately triggered it. However, that it not an argument against templates, it is an argument for improved error reporting. And hopefully, one day we'll get that. This is not the first time that you've argued that some feature or strategy is bad because today's D compiler isn't good enough, but you need to remember that tomorrow's D compiler will be better. That's life on the cutting edge.
May 11 2008
Janice Caron, el 11 de mayo a las 12:53 me escribiste:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I think you can use Token Strings[1] in D2 to have better error reporting. It justs do lexical analisys, but it's something: sort!(q{a > b})(array); [1] http://www.digitalmars.com/d/2.0/lex.html -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ ---------------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------------- Señor, usted es militar? - No, no. Tiene un hermano militar? - No, no, no. Tiene algún pariente militar? - No, no, no, no. Tiene algún amigo Ãntimo militar? - No, no, pero por qué me lo pregunta? Porque me está pisando el pie. -- Tato vs. Tato (1980, Gobierno de Videla)also, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere.On that point, I concede. The reporting of template errors could certainly be improved. More than once I have wanted to see the "stack trace" of errors, working back from the innermost, to the line of code that ultimately triggered it. However, that it not an argument against templates, it is an argument for improved error reporting. And hopefully, one day we'll get that. This is not the first time that you've argued that some feature or strategy is bad because today's D compiler isn't good enough, but you need to remember that tomorrow's D compiler will be better. That's life on the cutting edge.
May 12 2008
Leandro Lucarella wrote:Janice Caron, el 11 de mayo a las 12:53 me escribiste:this was already debated thoroughly, But I'll state my POV again. I don't like the use of strings here at all. in any form. D is a strongly typed language which I like and all those string tricks although cool are bad IMO. my issues with the use of this syntax: a) what if I had a typo? what if i typed n instead of b (they are next to each other on my keyboard)? your solution or any other string solution cannot check this, since it is syntactically valid D code inside the string. b) if I want to compare q{a.member > b.member} there is no check that member exists, or any other type checking. the error would occur in the template code itself not in the string in both of the above examples. c) what if I forgot to type the "q"? d) most IDEs mark strings with a different color or something like that so I treat them automatically as data and not code. ( this is a minor issue, since IDEs could be made to work differently. but, my sub-conscience response to strings as "not code" is harder to fix) the only benefit to this is a slight performance benefit (though a good compiler could provide the same performance by inlining a function call) and a slightly shorter syntax which is a personal preference since you type less but IMHO it becomes less readable and less obvious. I prefer: sort!((int a, int b) {return a.member > b.member;})(array); this is more typing but provides all the checks and is more readable. Think of the poor guy that will need to do maintenance on your code years from now. now the string is a cool quick'n'dirty solution to the problem at hand but in the future that other programmer will have no idea what those "a" and "b" are. especially since you didn't write the types of them. also the performance could be the same if I used a function instead of a delegate allowing the compiler to inline it (hopefully). since this is identical to: sort(array, (int a, int b) {return a.member > b.member;}); there is no benefit in making it a template, IMO. side note: using a feature of arrays in D this could be also rewritten as: array.sort((int a, int b) {return a.member > b.member;}); Again, this is only my opinion (and I do prefer higher level solutions and think the compler/linker can do a better job optimizing than I can). you do not have to agree with this view, and there are already people that disagree.On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I think you can use Token Strings[1] in D2 to have better error reporting. It justs do lexical analisys, but it's something: sort!(q{a > b})(array); [1] http://www.digitalmars.com/d/2.0/lex.htmlalso, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere.On that point, I concede. The reporting of template errors could certainly be improved. More than once I have wanted to see the "stack trace" of errors, working back from the innermost, to the line of code that ultimately triggered it. However, that it not an argument against templates, it is an argument for improved error reporting. And hopefully, one day we'll get that. This is not the first time that you've argued that some feature or strategy is bad because today's D compiler isn't good enough, but you need to remember that tomorrow's D compiler will be better. That's life on the cutting edge.
May 12 2008
On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I prefer: sort!((int a, int b) {return a.member > b.member;})(array);ints don't have a member variable called .member, so presumably, you must have meant something like class C { int member; } C[] array; sort!(C a, C b){return a.member > b.member;})(array);this is ... more readable.No it isn't. ;-)but in the future that other programmer will have no idea what those "a" and "b" are. especially since you didn't write the types of them.Incorrect. "a" and "b" are the placeholders for the things being compared. This is well documented in the documentation for sort. The only way anyone could not know what the "a" and "b" were would be if they hadn't RTFM. The type of a is the type of the array element. This is obvious.there is no benefit in making it a template, IMO.The benefit is choice.
May 12 2008
Janice Caron wrote:On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:right. Thanks for the fix. I'm not that good with examples.I prefer: sort!((int a, int b) {return a.member > b.member;})(array);ints don't have a member variable called .member, so presumably, you must have meant something like class C { int member; } C[] array; sort!(C a, C b){return a.member > b.member;})(array);again, obvious to you, not that dude five years from now reading your code. documented is not the same as compiler checked, and we both know that documentation could get out of sync with the code. also, why do I have to go and search the docs if the code is in front of me? at my work we have a very large code base in Fortran, which has to be linked to every program. I don't know Fortran and don't need to since I use c++. all those things there that where so obvious to the Fortran coders 15 years ago look to me like raw binary code. they've used various low-level tricks that no one understands and that aren't properly documented. if I get a linking error with those Fortran objects I could spend days trying to figure it out. I can look at the code and try to understand what's going on, but I don't know Fortran so every bit of readability helps. replace Fortran with D in this (real life) example and try to understand that it's not so easy as you make it.this is ... more readable.No it isn't. ;-)but in the future that other programmer will have no idea what those "a" and "b" are. especially since you didn't write the types of them.Incorrect. "a" and "b" are the placeholders for the things being compared. This is well documented in the documentation for sort. The only way anyone could not know what the "a" and "b" were would be if they hadn't RTFM. The type of a is the type of the array element. This is obvious.choice of what? You've said so yourself, both templated and non-templated versions of the sort-with-delegate are identical. you want those string templates (which i don't like) than fine, define them as templates and i won't use them. but the version with the delegate is needlessly a template. I guess I wouldn't object as much if D's syntax for calling templated code weren't different from regular functions which would bring a more uniform syntax (take a look at macros in Nemerle). Does it irritate only me? maybe I am the odd one here. I just don't want to care if one array function is a generic template, while another isn't. ideally (not possible in current D) both forms should have the same syntax, so the end user (me) doesn't need to know nor care about what sort of implementation was chosen.there is no benefit in making it a template, IMO.The benefit is choice.
May 12 2008
Somebody please throw Yigal's towel before Janice and the karate girl beat him into a pulp. Going by his responses he has an eye closed already.
May 12 2008
On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:> The type of a is the type of the array element. This is obvious. again, obvious to you, not that dude five years from now reading your code. documented is not the same as compiler checked, and we both know that documentation could get out of sync with the code.I'm talking about the documentation for std.algorithm.sort. What are you talking about? It is perfectly reasonable to expect that anyone using std.algorithm.sort will have read the documentation on how to use it.also, why do I have to go and search the docs if the code is in front of me?You don't. You learn how to use std.algorithm.sort, and then you use it, going back to the docs only occasionally. It's the same with anything. If I want to use a std::vector in C++, I'd better have read up on what std::vectors are and how to use them first. So yes, it's obvious what the "a" and "b" mean.Of everything. Of having it do whatever you want, whatever that is.The benefit is choice.choice of what?You've said so yourself, both templated and non-templated versions of the sort-with-delegate are identical.I have said no such thing! (And I take great offense at being misquoted) I may have said that *one particular instantiation* of the template is identical to the delegate version. Fair enough. But I can also make different instantiations.you want those string templates (which i don't like) than fine, define them as templates and i won't use them. but the version with the delegate is needlessly a template.There /is/ no "version with the delegate". But you can /make/ a version with a delegate out of the template. And those string parameters are brilliant! Today I wanted to strip out all underscores from a string. So I sat down to write the function - it's not really hard, after all - but then, with a flash of inspiration, I realised the function already exists. It is: filter!("a != '_'")(s); That is just /so/ amazing.ideally (not possible in current D) both forms should have the same syntax, so the end user (me) doesn't need to know nor care about what sort of implementation was chosen.You haven't heard of aliases or wrapper functions then?
May 12 2008
Janice Caron wrote:On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I am talking about *one particular instantiation* of the template!> The type of a is the type of the array element. This is obvious. again, obvious to you, not that dude five years from now reading your code. documented is not the same as compiler checked, and we both know that documentation could get out of sync with the code.I'm talking about the documentation for std.algorithm.sort. What are you talking about? It is perfectly reasonable to expect that anyone using std.algorithm.sort will have read the documentation on how to use it.also, why do I have to go and search the docs if the code is in front of me?You don't. You learn how to use std.algorithm.sort, and then you use it, going back to the docs only occasionally. It's the same with anything. If I want to use a std::vector in C++, I'd better have read up on what std::vectors are and how to use them first. So yes, it's obvious what the "a" and "b" mean.Of everything. Of having it do whatever you want, whatever that is.The benefit is choice.choice of what?You've said so yourself, both templated and non-templated versions of the sort-with-delegate are identical.I have said no such thing! (And I take great offense at being misquoted) I may have said that *one particular instantiation* of the template is identical to the delegate version. Fair enough. But I can also make different instantiations.Again, this is exactly what I want. this should be in the standard library.you want those string templates (which i don't like) than fine, define them as templates and i won't use them. but the version with the delegate is needlessly a template.There /is/ no "version with the delegate". But you can /make/ a version with a delegate out of the template.And those string parameters are brilliant! Today I wanted to strip out all underscores from a string. So I sat down to write the function - it's not really hard, after all - but then, with a flash of inspiration, I realised the function already exists. It is: filter!("a != '_'")(s); That is just /so/ amazing.for string manipulation I agree that this is useful.I meant possible *directly* not via wrappers. example time: I want the following API: (I don't care now for the internal implementation and the wrapper functions/aliases should be provided by the standard library) array.sort; //same as array.sort(ASC); array.sort(DESC); array.sort(someStaticComperator); array.sort(aDelegateComperator); array.someOtherfunction(params); List.sort; someOtherCollection.sort; int.sort; //compile time error, no sort method for int etc...ideally (not possible in current D) both forms should have the same syntax, so the end user (me) doesn't need to know nor care about what sort of implementation was chosen.You haven't heard of aliases or wrapper functions then?
May 12 2008
2008/5/13 Yigal Chripun <yigal100 gmail.com>:I want the following API: (I don't care now for the internal implementation and the wrapper functions/aliases should be provided by the standard library) array.sort; //same as array.sort(ASC);That one already works.array.sort(DESC);alias std.algorithm.sort!("b<a") sort_reverse; array.sort_reverse; Is that close enough?array.sort(someStaticComperator); array.sort(aDelegateComperator); array.someOtherfunction(params);That will never be possible either with or without templates. Guess why? Well, to save you guessing, I'll tell you. It's because "sort" is a built-in property of D's dynamic arrays. (Like length and ptr are built in). In general, we can define a function foo upon arrays which can be called as either foo(array,otherParams); array.foo(otherParams); But unfortunately, you can't do that if the function happens to be named "sort". (Likewise "length", "ptr", "init", and so on). As I said, this is /not/ a limitation of templates. If Walter were to remove the built-in sort property of arrays, then all of a sudden you would have: array.sort(someStaticComperator); array.sort(aDelegateComperator); which I /think/ would work, because of type-deduction. (The compiler will match it with sort!(delegate)). If it doesn't, then you could file a bugzilla report that type deduction fails, and eventually it will be fixed. In short, the solution to your problem is to REMOVE the built-in property "sort" from arrays, so that libary versions can take over.
May 13 2008
Janice Caron wrote:2008/5/13 Yigal Chripun <yigal100 gmail.com>:I want this alias in the library. I'd prefer a sort that received some enum (also incorporating the stable vs non-stable enum constants) but I can settle for this, _if_ it were present in the library. Those are the small things that make a difference. after all D does provide a string alias too.I want the following API: (I don't care now for the internal implementation and the wrapper functions/aliases should be provided by the standard library) array.sort; //same as array.sort(ASC);That one already works.array.sort(DESC);alias std.algorithm.sort!("b<a") sort_reverse; array.sort_reverse; Is that close enough?than, either the buit-in sort needs to be removed, or enhanced (thus removing the need for the library sort), or D needs to make it possible to overload built-in properties.array.sort(someStaticComperator); array.sort(aDelegateComperator); array.someOtherfunction(params);That will never be possible either with or without templates. Guess why? Well, to save you guessing, I'll tell you. It's because "sort" is a built-in property of D's dynamic arrays. (Like length and ptr are built in). In general, we can define a function foo upon arrays which can be called as either foo(array,otherParams); array.foo(otherParams); But unfortunately, you can't do that if the function happens to be named "sort". (Likewise "length", "ptr", "init", and so on). As I said, this is /not/ a limitation of templates. If Walter were to remove the built-in sort property of arrays, then all of a sudden you would have: array.sort(someStaticComperator); array.sort(aDelegateComperator);which I /think/ would work, because of type-deduction. (The compiler will match it with sort!(delegate)). If it doesn't, then you could file a bugzilla report that type deduction fails, and eventually it will be fixed. In short, the solution to your problem is to REMOVE the built-in property "sort" from arrays, so that libary versions can take over.OK, that is all I really wanted in the first place. my issue was with the syntax. if it is implemented with templates but I get to use the above syntax than I'm a 100% satisfied customer. :) also, I didn't mention it above but this also needs to support anonymous delegates too: array.sort((C a, C b) {return a.val < b.val;}); I assume this works too. correct me if this is wrong.
May 13 2008
2008/5/13 Yigal Chripun <yigal100 gmail.com>:> alias std.algorithm.sort!("b<a") sort_reverse; > array.sort_reverse; > > Is that close enough? I want this alias in the library.It can't be in the library, because if it were in the library it would cause the template to instantiate. We don't want it to instantiate if it's not needed. (Currently, an alias causes instantiation, even if the alias is not subsequently used. I don't like that behaviour, but that's how it is right now).D needs to make it possible to overload built-in properties.You can't overload something that isn't a class. Overloading requires inheritance.
May 13 2008
Janice Caron wrote:2008/5/13 Yigal Chripun <yigal100 gmail.com>:I don't like it either.> alias std.algorithm.sort!("b<a") sort_reverse; > array.sort_reverse; > > Is that close enough? I want this alias in the library.It can't be in the library, because if it were in the library it would cause the template to instantiate. We don't want it to instantiate if it's not needed. (Currently, an alias causes instantiation, even if the alias is not subsequently used. I don't like that behaviour, but that's how it is right now).either D should provide special treatment for this or it shoud use one of the other options like removing the built-in sort. Besides we are discussing changes to D, aren't we? although I'm still not sure that parameterizing sort with an alias for the delegate makes less instances than parameterizing sort with array element type. could you explain and compare the two options in this regard? (this of course doesn't take into account the string mixin instances) of course, this is secondary to the syntax issue for me.D needs to make it possible to overload built-in properties.You can't overload something that isn't a class. Overloading requires inheritance.
May 13 2008
2008/5/13 Frits van Bommel <fvbommel remwovexcapss.nl>:Overloading works fine without a class: You're probably thinking of overriding, which requires a virtual method in a base class.If you'd read a few posts on you would have realised I corrected that mistake within /seconds/ of the original post. :-)
May 13 2008
Janice Caron wrote:2008/5/13 Frits van Bommel <fvbommel remwovexcapss.nl>:And if you'd replied to your own post with that correction instead of re-replying to the parent post, I *would* have read that first :P. Anyway, I'd already canceled my post when you posted yours...Overloading works fine without a class: You're probably thinking of overriding, which requires a virtual method in a base class.If you'd read a few posts on you would have realised I corrected that mistake within /seconds/ of the original post. :-)
May 13 2008
2008/5/13 Janice Caron <caron800 googlemail.com>:> D needs to make it possible > to overload built-in properties. You can't overload something that isn't a class. Overloading requires inheritance.My apologies - I think we both meant "override", not "overload". :-) You can't overLOAD unless you are the author of the struct/class in question.
May 13 2008
Janice Caron wrote:2008/5/13 Janice Caron <caron800 googlemail.com>:agreed :)> D needs to make it possible > to overload built-in properties. You can't overload something that isn't a class. Overloading requires inheritance.My apologies - I think we both meant "override", not "overload". :-) You can't overLOAD unless you are the author of the struct/class in question.
May 13 2008
Janice Caron wrote:2008/5/13 Yigal Chripun <yigal100 gmail.com>:Wow, then tango.core.Array.sort must work by magic! I had no idea Tango had such powers: C:\code\src\d\test>cat test.d import tango.core.Array; void main() { int[] buf = ([1,2,3]).dup; buf.sort( (int a, int b){ return a < b; } ); } C:\code\src\d\test>dmd test C:\code\src\d\test> SeanI want the following API: (I don't care now for the internal implementation and the wrapper functions/aliases should be provided by the standard library) array.sort; //same as array.sort(ASC);That one already works.array.sort(DESC);alias std.algorithm.sort!("b<a") sort_reverse; array.sort_reverse; Is that close enough?array.sort(someStaticComperator); array.sort(aDelegateComperator); array.someOtherfunction(params);That will never be possible either with or without templates. Guess why? Well, to save you guessing, I'll tell you. It's because "sort" is a built-in property of D's dynamic arrays. (Like length and ptr are built in). In general, we can define a function foo upon arrays which can be called as either foo(array,otherParams); array.foo(otherParams); But unfortunately, you can't do that if the function happens to be named "sort". (Likewise "length", "ptr", "init", and so on). As I said, this is /not/ a limitation of templates.
May 13 2008
Yigal Chripun Wrote:Yigal-san this is incorrect again. I am sorry!! Did you do the disassembly I sent you? It is very simple to change to use alias (not string). You can see how sort makes direct call to the alias, not indirect! It is always good to test first and write after.The benefit is choice.choice of what? You've said so yourself, both templated and non-templated versions of the sort-with-delegate are identical.you want those string templates (which i don't like) than fine, define them as templates and i won't use them. but the version with the delegate is needlessly a template. I guess I wouldn't object as much if D's syntax for calling templated code weren't different from regular functions which would bring a more uniform syntax (take a look at macros in Nemerle). Does it irritate only me? maybe I am the odd one here. I just don't want to care if one array function is a generic template, while another isn't. ideally (not possible in current D) both forms should have the same syntax, so the end user (me) doesn't need to know nor care about what sort of implementation was chosen.There are many things you write and then write is your opinion and others can disagree. It is good when opinion is about subjective things. And opinions are equal on subjective things like the syntax you write about. But some times you write just wrong things and do not want to try and see what is true. It does not work to say it is an opinion and others can disagree. Wrong is not an opinion. Wrong and right are not equal. It is good to try find the truth, not write wrong things as opinions. If you want truth take the disassembly and see. Thank you, Dee Girl
May 12 2008
Dee Girl wrote: <snip> maybe I still didn't explain myself properly. I've used the term "sort-with-delegates" meaning a specific instance of sort when given a delegate as input. I thought this was clear enough, otherwise I would have simply said "sort". before you start sending me assembly code, let's go over it again and make sure we both understand each other: you take the sort template, stick in a delegate, mix for five minutes let is simmer for one more and viola: you got the exact same thing if you would have written sort as a regular function which receives a delegate parameter. the template takes the symbol it receives (the delegate name) and "embeds" it in the sort instance. both ways you get: 1) a call to sort 2) a call to the delegate the only difference is the number of parameters the sort function receives when called. the regular sort function can run different delegates at runtime (this is what you call "dynamic". the template has the call to the specific delegate embedded so for different delegates you need different sort instances.
May 12 2008
Yigal Chripun Wrote:Dee Girl wrote: <snip> maybe I still didn't explain myself properly. I've used the term "sort-with-delegates" meaning a specific instance of sort when given a delegate as input. I thought this was clear enough, otherwise I would have simply said "sort". before you start sending me assembly code, let's go over it again and make sure we both understand each other: you take the sort template, stick in a delegate, mix for five minutes let is simmer for one more and viola: you got the exact same thing if you would have written sort as a regular function which receives a delegate parameter. the template takes the symbol it receives (the delegate name) and "embeds" it in the sort instance. both ways you get: 1) a call to sort 2) a call to the delegate the only difference is the number of parameters the sort function receives when called. the regular sort function can run different delegates at runtime (this is what you call "dynamic". the template has the call to the specific delegate embedded so for different delegates you need different sort instances.Good. I think I understand. I want to be very sure, so I have a question from this code. int array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate? 2. Is the call inside sort direct or indirect? Thank you, Dee Girl
May 12 2008
== Quote from Dee Girl (deegirl noreply.com)'s articleYigal Chripun Wrote:It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.Dee Girl wrote: <snip> maybe I still didn't explain myself properly. I've used the term "sort-with-delegates" meaning a specific instance of sort when given a delegate as input. I thought this was clear enough, otherwise I would have simply said "sort". before you start sending me assembly code, let's go over it again and make sure we both understand each other: you take the sort template, stick in a delegate, mix for five minutes let is simmer for one more and viola: you got the exact same thing if you would have written sort as a regular function which receives a delegate parameter. the template takes the symbol it receives (the delegate name) and "embeds" it in the sort instance. both ways you get: 1) a call to sort 2) a call to the delegate the only difference is the number of parameters the sort function receives when called. the regular sort function can run different delegates at runtime (this is what you call "dynamic". the template has the call to the specific delegate embedded so for different delegates you need different sort instances.Good. I think I understand. I want to be very sure, so I have a question from this code. int array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?2. Is the call inside sort direct or indirect? Thank you, Dee GirlDo you mean inline or not inline? It's hard to say. DMD's inlining mechanism is a bit quirky, so it could go either way. For example, I've had struct member functions which would be inlined if static and not inlined if not static, and other member functions which were the reverse. I haven't found any way to be sure other than trying it out and examining the ASM from the object file. Sean
May 12 2008
Sean Kelly Wrote:== Quote from Dee Girl (deegirl noreply.com)'s articleMaybe you did not read previous discussion. Choice of comparator can be made at runtime. sort!(dg) works for a dynamic delegate dg. Just try it! It was hard to believe for me. The way D compiler insantiates sort is ingenious. This makes sort very powerful because it works with both static and dynamic code at the same time! I never found that in a language.Yigal Chripun Wrote:It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.Dee Girl wrote: <snip> maybe I still didn't explain myself properly. I've used the term "sort-with-delegates" meaning a specific instance of sort when given a delegate as input. I thought this was clear enough, otherwise I would have simply said "sort". before you start sending me assembly code, let's go over it again and make sure we both understand each other: you take the sort template, stick in a delegate, mix for five minutes let is simmer for one more and viola: you got the exact same thing if you would have written sort as a regular function which receives a delegate parameter. the template takes the symbol it receives (the delegate name) and "embeds" it in the sort instance. both ways you get: 1) a call to sort 2) a call to the delegate the only difference is the number of parameters the sort function receives when called. the regular sort function can run different delegates at runtime (this is what you call "dynamic". the template has the call to the specific delegate embedded so for different delegates you need different sort instances.Good. I think I understand. I want to be very sure, so I have a question from this code. int array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?Question was not about inlining. It was about indirect call with a pointer to function or a regular call. The regular call could be inlined but the pointer call is much harder. The answer is deterministic and very interesting. I think I undestand now how the D compiler generates functions from templates. Thank you, Dee Girl2. Is the call inside sort direct or indirect? Thank you, Dee GirlDo you mean inline or not inline? It's hard to say. DMD's inlining mechanism is a bit quirky, so it could go either way. For example, I've had struct member functions which would be inlined if static and not inlined if not static, and other member functions which were the reverse. I haven't found any way to be sure other than trying it out and examining the ASM from the object file.
May 12 2008
== Quote from Dee Girl (deegirl noreply.com)'s articleSean Kelly Wrote:works for a dynamic delegate dg. Just try it! It was hard to believe for me. The way D compiler insantiates sort is ingenious. This makes sort very powerful because it works with both static and dynamic code at the same time! I never found that in a language. So you're saying I could do this: void myfunc( bool delegate(int,int) dg ) { int[] buf = [1,2,3,4,5].dup; sort!(dg)( buf ); } I thought alias parameters had to be resolvable at compile-time. Interesting.== Quote from Dee Girl (deegirl noreply.com)'s articleMaybe you did not read previous discussion. Choice of comparator can be made at runtime. sort!(dg)Yigal Chripun Wrote:It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.Dee Girl wrote: <snip> maybe I still didn't explain myself properly. I've used the term "sort-with-delegates" meaning a specific instance of sort when given a delegate as input. I thought this was clear enough, otherwise I would have simply said "sort". before you start sending me assembly code, let's go over it again and make sure we both understand each other: you take the sort template, stick in a delegate, mix for five minutes let is simmer for one more and viola: you got the exact same thing if you would have written sort as a regular function which receives a delegate parameter. the template takes the symbol it receives (the delegate name) and "embeds" it in the sort instance. both ways you get: 1) a call to sort 2) a call to the delegate the only difference is the number of parameters the sort function receives when called. the regular sort function can run different delegates at runtime (this is what you call "dynamic". the template has the call to the specific delegate embedded so for different delegates you need different sort instances.Good. I think I understand. I want to be very sure, so I have a question from this code. int array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?The regular call could be inlined but the pointer call is much harder. The answer is deterministic and very interesting. I think I undestand now how the D compiler generates functions from templates. Thank you, Dee Girl Harder but still possible. DMD doesn't do this right now, but Walter has said it may in the future. SeanQuestion was not about inlining. It was about indirect call with a pointer to function or a regular call.2. Is the call inside sort direct or indirect? Thank you, Dee GirlDo you mean inline or not inline? It's hard to say. DMD's inlining mechanism is a bit quirky, so it could go either way. For example, I've had struct member functions which would be inlined if static and not inlined if not static, and other member functions which were the reverse. I haven't found any way to be sure other than trying it out and examining the ASM from the object file.
May 12 2008
Sean Kelly Wrote:== Quote from Dee Girl (deegirl noreply.com)'s articleThank you for the information. But still there is no answer to my questions. It seems nobody is interested in the actual answer. So I will keep it secret. :x Dee GirlSean Kelly Wrote:works for a dynamic delegate dg. Just try it! It was hard to believe for me. The way D compiler insantiates sort is ingenious. This makes sort very powerful because it works with both static and dynamic code at the same time! I never found that in a language. So you're saying I could do this: void myfunc( bool delegate(int,int) dg ) { int[] buf = [1,2,3,4,5].dup; sort!(dg)( buf ); } I thought alias parameters had to be resolvable at compile-time. Interesting.== Quote from Dee Girl (deegirl noreply.com)'s articleMaybe you did not read previous discussion. Choice of comparator can be made at runtime. sort!(dg)Yigal Chripun Wrote:It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.Dee Girl wrote: <snip> maybe I still didn't explain myself properly. I've used the term "sort-with-delegates" meaning a specific instance of sort when given a delegate as input. I thought this was clear enough, otherwise I would have simply said "sort". before you start sending me assembly code, let's go over it again and make sure we both understand each other: you take the sort template, stick in a delegate, mix for five minutes let is simmer for one more and viola: you got the exact same thing if you would have written sort as a regular function which receives a delegate parameter. the template takes the symbol it receives (the delegate name) and "embeds" it in the sort instance. both ways you get: 1) a call to sort 2) a call to the delegate the only difference is the number of parameters the sort function receives when called. the regular sort function can run different delegates at runtime (this is what you call "dynamic". the template has the call to the specific delegate embedded so for different delegates you need different sort instances.Good. I think I understand. I want to be very sure, so I have a question from this code. int array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?The regular call could be inlined but the pointer call is much harder. The answer is deterministic and very interesting. I think I undestand now how the D compiler generates functions from templates. Thank you, Dee Girl Harder but still possible. DMD doesn't do this right now, but Walter has said it may in the future.Question was not about inlining. It was about indirect call with a pointer to function or a regular call.2. Is the call inside sort direct or indirect? Thank you, Dee GirlDo you mean inline or not inline? It's hard to say. DMD's inlining mechanism is a bit quirky, so it could go either way. For example, I've had struct member functions which would be inlined if static and not inlined if not static, and other member functions which were the reverse. I haven't found any way to be sure other than trying it out and examining the ASM from the object file.
May 12 2008
Dee Girl wrote: <snip>1. Is the code inside sort!() as powerful as delegate? 2. Is the call inside sort direct or indirect? Thank you, Dee Girlwell, you've been answered by an expert (Thanks Sean!) I didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :) to answer your questions: as I understand it, a delegate is internally a struct with two pointers: a context pointer and a function pointer. the context pointer has the address of the outer function for nested functions, or the this pointer for methods. so, when passing a delegate you pass this struct. based on that, I think that the answer to your second question is that the call is indirect. this is true for both cases, otherwise that trick of using a sort!() instance inside a mySort function wouldn't work. all you pass to the template version is just a D symbol, in our case the name of the delegate. in both cases you need to call that delegate. you can pass regular function pointers to the template too (they are written either with the function reserved word, or with the C syntax) and those are simpler to inline. you can overload a non-templated sort function to receive those as well. I hope, some day function pointers would be implicitly castable to delegates (just make the context pointer contain null) and this will remove the need for providing two identical functions (one for delegates and one for function pointers) btw, another way of changing the delegate for a specific sort!() instance is simply to assign it; no need for a wrapper function. delegate bool(int a, int b) dg; dg = aDelegate; sort!(dg)(array); dg = otherDelegate; sort!(dg)(array); // the same instance should be used.
May 13 2008
Yigal Chripun Wrote:Dee Girl wrote: <snip>Definitely Sean is a excellent expert. But my understanding of his answer is a bit different. I see he did not know the answer to the first question. So I gave the answer to him, and he said it is something he did not know before. Then he missed the second question.1. Is the code inside sort!() as powerful as delegate? 2. Is the call inside sort direct or indirect? Thank you, Dee Girlwell, you've been answered by an expert (Thanks Sean!)I didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :)I think this is great but it is much easy said than done. Inlining an indirect call means the callee must be inlined too. Or at least duplicated. There is complicated analysis that shows which indirect calls can be inlined that way. Not all can.to answer your questions: as I understand it, a delegate is internally a struct with two pointers: a context pointer and a function pointer. the context pointer has the address of the outer function for nested functions, or the this pointer for methods. so, when passing a delegate you pass this struct. based on that, I think that the answer to your second question is that the call is indirect.This answer is incorrect. The call is direct. I clarify: this is not matter of optimization or tricks. It is matter of principle. It is coming from the very clever way Walter instantiates templates with alias arguments. He specializes the template at the call site. I wish I had his idea for my project but not it is too late ^_^ I think this is very important feature of the D programming language.this is true for both cases, otherwise that trick of using a sort!() instance inside a mySort function wouldn't work. all you pass to the template version is just a D symbol, in our case the name of the delegate. in both cases you need to call that delegate.In the example I wrote it is not the name of the delegate. It is the name of nested function. It is anonymous but still has internal name.you can pass regular function pointers to the template too (they are written either with the function reserved word, or with the C syntax) and those are simpler to inline. you can overload a non-templated sort function to receive those as well.If a pointer to function is passed then template function is instantiated with that pointer type. Discussion on when that can be inlined takes to another subject.I hope, some day function pointers would be implicitly castable to delegates (just make the context pointer contain null) and this will remove the need for providing two identical functions (one for delegates and one for function pointers) btw, another way of changing the delegate for a specific sort!() instance is simply to assign it; no need for a wrapper function. delegate bool(int a, int b) dg; dg = aDelegate; sort!(dg)(array); dg = otherDelegate; sort!(dg)(array); // the same instance should be used.That is correct. The same instance will be used. Thank you, Dee Girl
May 13 2008
Dee Girl wrote:OK. so what? either way, It's the compiler/linker job to make optimizations. it sure is more capable and has more info than I am. I'm not implementing the D compiler, I'm using it. my POV is that of the user. I believe Walter is a genius compiler writer and that if it's possible he'll make it happen.I didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :)I think this is great but it is much easy said than done. Inlining an indirect call means the callee must be inlined too. Or at least duplicated. There is complicated analysis that shows which indirect calls can be inlined that way. Not all can.I'm confused by your terminology. anyway, this is not really that important to me, again I trust Walter to make the compiler produce the best possible code. I meant that the sort instance calls the delegate anyway and does not inline it. I'm not sure what you mean in direct vs. indirect and why this is important. I thought that the important thing for performance was whether the call gets inlined or not. I'm saying here that both options (the template instance and the regular function) do the same regarding inlining the delegate.to answer your questions: as I understand it, a delegate is internally a struct with two pointers: a context pointer and a function pointer. the context pointer has the address of the outer function for nested functions, or the this pointer for methods. so, when passing a delegate you pass this struct. based on that, I think that the answer to your second question is that the call is indirect.This answer is incorrect. The call is direct. I clarify: this is not matter of optimization or tricks. It is matter of principle. It is coming from the very clever way Walter instantiates templates with alias arguments. He specializes the template at the call site. I wish I had his idea for my project but not it is too late ^_^ I think this is very important feature of the D programming language.see Janice's reply. effectively this is the same thing.this is true for both cases, otherwise that trick of using a sort!() instance inside a mySort function wouldn't work. all you pass to the template version is just a D symbol, in our case the name of the delegate. in both cases you need to call that delegate.In the example I wrote it is not the name of the delegate. It is the name of nested function. It is anonymous but still has internal name.you can pass regular function pointers to the template too (they are written either with the function reserved word, or with the C syntax) and those are simpler to inline. you can overload a non-templated sort function to receive those as well.If a pointer to function is passed then template function is instantiated with that pointer type. Discussion on when that can be inlined takes to another subject.I hope, some day function pointers would be implicitly castable to delegates (just make the context pointer contain null) and this will remove the need for providing two identical functions (one for delegates and one for function pointers) btw, another way of changing the delegate for a specific sort!() instance is simply to assign it; no need for a wrapper function. delegate bool(int a, int b) dg; dg = aDelegate; sort!(dg)(array); dg = otherDelegate; sort!(dg)(array); // the same instance should be used.That is correct. The same instance will be used. Thank you, Dee Girl
May 13 2008
Yigal Chripun Wrote:Dee Girl wrote:But this is the interesting thing. Walter is a genius compiler writer and he made it happen already. Now. Your compiler has it already. And it has it in very elegant form. The point I tried many times to clarify is that Walter has done the great work already. But it is not in the documents. Maybe only Walter knows it now. And Andrei because he wrote std.algorithm exactly to use the feature.OK. so what? either way, It's the compiler/linker job to make optimizations. it sure is more capable and has more info than I am. I'm not implementing the D compiler, I'm using it. my POV is that of the user. I believe Walter is a genius compiler writer and that if it's possible he'll make it happen.I didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :)I think this is great but it is much easy said than done. Inlining an indirect call means the callee must be inlined too. Or at least duplicated. There is complicated analysis that shows which indirect calls can be inlined that way. Not all can.I am using standard terminology. But maybe my sentences are wrong.I'm confused by your terminology.to answer your questions: as I understand it, a delegate is internally a struct with two pointers: a context pointer and a function pointer. the context pointer has the address of the outer function for nested functions, or the this pointer for methods. so, when passing a delegate you pass this struct. based on that, I think that the answer to your second question is that the call is indirect.This answer is incorrect. The call is direct. I clarify: this is not matter of optimization or tricks. It is matter of principle. It is coming from the very clever way Walter instantiates templates with alias arguments. He specializes the template at the call site. I wish I had his idea for my project but not it is too late ^_^ I think this is very important feature of the D programming language.anyway, this is not really that important to me, again I trust Walter to make the compiler produce the best possible code. I meant that the sort instance calls the delegate anyway and does not inline it. I'm not sure what you mean in direct vs. indirect and why this is important. I thought that the important thing for performance was whether the call gets inlined or not.It is important because of this. Direct call is trivially inlined. Indirect call is maybe 1000 times harder to inline and sometimes impossible. By direct call I mean you know the address of the function at compilation. By indirect call I mean you have a pointer with the address of the function.I'm saying here that both options (the template instance and the regular function) do the same regarding inlining the delegate.This sentence is incorrect. The template instance works very different from the regular function.They are not the same thing. I have explained in other post. Thank you, Dee Girlsee Janice's reply. effectively this is the same thing.this is true for both cases, otherwise that trick of using a sort!() instance inside a mySort function wouldn't work. all you pass to the template version is just a D symbol, in our case the name of the delegate. in both cases you need to call that delegate.In the example I wrote it is not the name of the delegate. It is the name of nested function. It is anonymous but still has internal name.
May 13 2008
Dee Girl wrote:It is important because of this. Direct call is trivially inlined. Indirect call is maybe 1000 times harder to inline and sometimes impossible. By direct call I mean you know the address of the function at compilation. By indirect call I mean you have a pointer with the address of the function.again, I'll leave this to Walter to figure out. the decision to inline is and should be the compiler's.you say that sort!(delegate) inlines the call to delegate? in that case: dg = dg1; sort!(dg)(..); dg = dg2; sort!(dg)(..); whould the above produce two different instances? with classes: class A { bool method(); } auto a = new A; auto b = new B; dg = &a.method; sort!(dg)(..); dg = &b.method; sort!(dg)(..); will this produce two instances? I understood till now that the sort!(delegate)(..) /may/ inline the delegate but it's not a must. on the other hand: sort(delegate,..) currently doesn't inline but /may/ do so in the future. in the end, both will /maybe/ provide inline capability. if this is true I don't see any difference.I'm saying here that both options (the template instance and the regular function) do the same regarding inlining the delegate.This sentence is incorrect. The template instance works very different from the regular function.
May 13 2008
2008/5/13 Yigal Chripun <yigal100 gmail.com>:<snip> Lots of stuff about inliningPlease don't confuse inlining with instantiation. This does not help the discussion. Whether or not a compiler inlines a function or not is a decision made at the call site, however the function body must still exist elsewhere in the object file, in case other functions in other modules need to call it non-inlined. If nothing calls it non-inlined, the linker should remove it when building the executable. This has nothing whatsoever to do with template instantiation. Inlining may or may not happen with either functions or delegates, but it has no bearing whatsoever on the "to template or not" discussion.
May 13 2008
Janice Caron wrote:2008/5/13 Yigal Chripun <yigal100 gmail.com>:you know, it's not easy to have two parallel conversations with two different persons about almost the same thing, while one is is making one point and another makes a different point. concurrency is hard!! Dee talks about inlining, and here I completely agree with you about:<snip> Lots of stuff about inliningPlease don't confuse inlining with instantiation. This does not help the discussion. Whether or not a compiler inlines a function or not is a decision made at the call site, however the function body must still exist elsewhere in the object file, in case other functions in other modules need to call it non-inlined. If nothing calls it non-inlined, the linker should remove it when building the executable. This has nothing whatsoever to do with template instantiation. Inlining may or may not happen with either functions or delegates, but it has no bearing whatsoever on the "to template or not" discussion.Inlining may or may not happen with either functions or delegates, but it has no bearing whatsoever on the "to template or not" discussion.for me the question is how a template parametrized on the entire delegate (i.e. an alias) compares with a template parametrized on the element type of the array (which is how I would have done this, I think) in regard to the number of instantiations of both solutions. I'd like to hear your explanation comparing the two. Thanks for your patience (is this spelled right?) --Yigal
May 13 2008
Dee Girl wrote:Yigal Chripun Wrote:In fact, I think this feature was probably added specifically because Andrei needed it for std.algorithm. It has never been discussed or documented anywhere, to my knowledge. SeanDee Girl wrote:But this is the interesting thing. Walter is a genius compiler writer and he made it happen already. Now. Your compiler has it already. And it has it in very elegant form. The point I tried many times to clarify is that Walter has done the great work already. But it is not in the documents. Maybe only Walter knows it now. And Andrei because he wrote std.algorithm exactly to use the feature.OK. so what? either way, It's the compiler/linker job to make optimizations. it sure is more capable and has more info than I am. I'm not implementing the D compiler, I'm using it. my POV is that of the user. I believe Walter is a genius compiler writer and that if it's possible he'll make it happen.I didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :)I think this is great but it is much easy said than done. Inlining an indirect call means the callee must be inlined too. Or at least duplicated. There is complicated analysis that shows which indirect calls can be inlined that way. Not all can.
May 13 2008
Sean Kelly wrote:Dee Girl wrote:I take it back. I created some test code for D 1.0 maybe a year ago that uses this feature, so it must have worked in D 1.0 at some point. I must be getting old--my memory is going :-p SeanYigal Chripun Wrote:In fact, I think this feature was probably added specifically because Andrei needed it for std.algorithm. It has never been discussed or documented anywhere, to my knowledge.Dee Girl wrote:But this is the interesting thing. Walter is a genius compiler writer and he made it happen already. Now. Your compiler has it already. And it has it in very elegant form. The point I tried many times to clarify is that Walter has done the great work already. But it is not in the documents. Maybe only Walter knows it now. And Andrei because he wrote std.algorithm exactly to use the feature.OK. so what? either way, It's the compiler/linker job to make optimizations. it sure is more capable and has more info than I am. I'm not implementing the D compiler, I'm using it. my POV is that of the user. I believe Walter is a genius compiler writer and that if it's possible he'll make it happen.I didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :)I think this is great but it is much easy said than done. Inlining an indirect call means the callee must be inlined too. Or at least duplicated. There is complicated analysis that shows which indirect calls can be inlined that way. Not all can.
May 18 2008
Dee Girl wrote:Yigal Chripun Wrote:Regarding the alias stuff I didn't know before. I don't suppose you know why it doesn't work in D 1.0? It isn't documented in the 2.0 spec so either it's a new omission or it's a feature which doesn't require a spec change and should therefore be supported in D 1.0 as well. As for the second question--you're right that I didn't understand what you were asking. Given the classic D 1.0 concept of alias template parameters I'd say a direct call occurred, but for some instances (like my example), that isn't possible. So I'd say it depends on what the alias refers to, though I haven't looked at the asm to be sure.Dee Girl wrote: <snip>Definitely Sean is a excellent expert. But my understanding of his answer is a bit different. I see he did not know the answer to the first question. So I gave the answer to him, and he said it is something he did not know before. Then he missed the second question.1. Is the code inside sort!() as powerful as delegate? 2. Is the call inside sort direct or indirect? Thank you, Dee Girlwell, you've been answered by an expert (Thanks Sean!)See above. I believe it's direct when it can be. Still a bit weird that a template can be instantiated with a non-static variable, but the approach is certainly sound so who am I to complain :-) SeanI didn't know that Walter is working on making inlining delegates possible, that this is indeed great news. :)I think this is great but it is much easy said than done. Inlining an indirect call means the callee must be inlined too. Or at least duplicated. There is complicated analysis that shows which indirect calls can be inlined that way. Not all can.to answer your questions: as I understand it, a delegate is internally a struct with two pointers: a context pointer and a function pointer. the context pointer has the address of the outer function for nested functions, or the this pointer for methods. so, when passing a delegate you pass this struct. based on that, I think that the answer to your second question is that the call is indirect.This answer is incorrect. The call is direct. I clarify: this is not matter of optimization or tricks. It is matter of principle. It is coming from the very clever way Walter instantiates templates with alias arguments. He specializes the template at the call site. I wish I had his idea for my project but not it is too late ^_^
May 13 2008
Sean Kelly wrote:So you're saying I could do this: void myfunc( bool delegate(int,int) dg ) { int[] buf = [1,2,3,4,5].dup; sort!(dg)( buf ); } I thought alias parameters had to be resolvable at compile-time. Interesting.Hum, I thought the same. And frankly I couldn't see how it could work otherwise, so it took me a while, and several experimentations, to figure out how it works. Apparently, if the alias parameter is local, it is as if the template body is instantiated in the local scope where the template instance occurs, and if the template contains a function, it is as if the function used an outer local variable (and thus the function becomes a nested function). After this, didn't took me long to cook up a bug using this feature :P : http://d.puremagic.com/issues/show_bug.cgi?id=2148 -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jun 10 2008
2008/5/13 Sean Kelly <sean invisibleduck.org>:It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
Janice Caron Wrote:2008/5/13 Sean Kelly <sean invisibleduck.org>:Even more simple. sort!(compare)(array); sort!(compare)(array); Decision depends on what compare is. Dee GirlIt depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
Janice Caron wrote:2008/5/13 Sean Kelly <sean invisibleduck.org>:I meant the way sort works in std.algorithm. But it appears that my answer was D 1.0-specific. I had no idea D 2.0 added the option to instantiate a template on an alias to a local variable. SeanIt depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
Sean Kelly wrote:Janice Caron wrote:It should work in D1.0, as well. It was added not long before D1.00 came out.2008/5/13 Sean Kelly <sean invisibleduck.org>:I meant the way sort works in std.algorithm. But it appears that my answer was D 1.0-specific. I had no idea D 2.0 added the option to instantiate a template on an alias to a local variable.It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
== Quote from Don (nospam nospam.com.au)'s articleSean Kelly wrote:It doesn't. Or more specifically, this doesn't work in D 1.0: void sort(alias cmp, T)( T buf ) { // pretend we're sorting cmp( buf[0], buf[1] ); } void doSort( int[] buf, bool delegate(int,int) cmp ) { sort!(cmp)( buf ); } SeanJanice Caron wrote:It should work in D1.0, as well. It was added not long before D1.00 came out.2008/5/13 Sean Kelly <sean invisibleduck.org>:I meant the way sort works in std.algorithm. But it appears that my answer was D 1.0-specific. I had no idea D 2.0 added the option to instantiate a template on an alias to a local variable.It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
Sean Kelly wrote:== Quote from Don (nospam nospam.com.au)'s articleOh, *that's* what you guys are talking about? Here's the bugzilla bug for it: http://d.puremagic.com/issues/show_bug.cgi?id=493 --bbSean Kelly wrote:It doesn't. Or more specifically, this doesn't work in D 1.0: void sort(alias cmp, T)( T buf ) { // pretend we're sorting cmp( buf[0], buf[1] ); } void doSort( int[] buf, bool delegate(int,int) cmp ) { sort!(cmp)( buf ); } SeanJanice Caron wrote:It should work in D1.0, as well. It was added not long before D1.00 came out.2008/5/13 Sean Kelly <sean invisibleduck.org>:I meant the way sort works in std.algorithm. But it appears that my answer was D 1.0-specific. I had no idea D 2.0 added the option to instantiate a template on an alias to a local variable.It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
Bill Baxter wrote:Sean Kelly wrote:And here I thought D 1.0 just didn't like my aliasing a local variable. Go figure. Sean== Quote from Don (nospam nospam.com.au)'s articleOh, *that's* what you guys are talking about? Here's the bugzilla bug for it: http://d.puremagic.com/issues/show_bug.cgi?id=493Sean Kelly wrote:It doesn't. Or more specifically, this doesn't work in D 1.0: void sort(alias cmp, T)( T buf ) { // pretend we're sorting cmp( buf[0], buf[1] ); } void doSort( int[] buf, bool delegate(int,int) cmp ) { sort!(cmp)( buf ); } SeanJanice Caron wrote:It should work in D1.0, as well. It was added not long before D1.00 came out.2008/5/13 Sean Kelly <sean invisibleduck.org>:I meant the way sort works in std.algorithm. But it appears that my answer was D 1.0-specific. I had no idea D 2.0 added the option to instantiate a template on an alias to a local variable.It depends what you mean by "powerful." Passing a comparator as a template parameter, as with sort!(), means that the choice of comparator must be made at compile-time rather than run-time. This may be problematic in some instances.I think you meant to say, the choice of comparator /may/ be made at compile-time. Not must. sort!(compare)(array); // compile time sort(compare, array); // run time
May 13 2008
2008/5/13 Dee Girl <deegirl noreply.com>:int array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?The code inside sort!() /is/ a delegate. It's an anonymous delegate, but its implementation is exactly the same as a delegate with a name. It's equivalent to int foo (int a, int b) { return a + x < b + x; }; sort!(foo)(array);
May 13 2008
Janice Caron Wrote:2008/5/13 Dee Girl <deegirl noreply.com>:Yes, excellent point. The codes are equivalent. But foo is not a delegate. It is a nested function. Like in Pascal. I am not sure how it can become delegate. Maybe Walter can answer because I see he writes here. Maybe when somebody takes address of foo. Dee Girlint array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?The code inside sort!() /is/ a delegate. It's an anonymous delegate, but its implementation is exactly the same as a delegate with a name. It's equivalent to int foo (int a, int b) { return a + x < b + x; }; sort!(foo)(array);
May 13 2008
Dee Girl wrote:Janice Caron Wrote:foo _is_ a delegate. there is no difference: As I explained in a different post, a delegate is simply two pointers where the context pointer either points to the surrounding function for nested functions or to the this pointer for methods.2008/5/13 Dee Girl <deegirl noreply.com>:Yes, excellent point. The codes are equivalent. But foo is not a delegate. It is a nested function. Like in Pascal. I am not sure how it can become delegate. Maybe Walter can answer because I see he writes here. Maybe when somebody takes address of foo. Dee Girlint array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?The code inside sort!() /is/ a delegate. It's an anonymous delegate, but its implementation is exactly the same as a delegate with a name. It's equivalent to int foo (int a, int b) { return a + x < b + x; }; sort!(foo)(array);
May 13 2008
Yigal Chripun Wrote:Dee Girl wrote:It is clear what is a delegate. It is pointer to a function and pointer to environment. Like in Lisp. But foo is not delegate. It is nested function. Like in Pascal. If you call foo from the same scope there is regular function call. Not delegate call! If you take address &foo it becomes delegate. (I just checked). D has very interesting scheme. I hope Walter writes here and confirms. But your explanation is wrong. I hope you are not offended. I am sorry, Dee GirlJanice Caron Wrote:foo _is_ a delegate. there is no difference: As I explained in a different post, a delegate is simply two pointers where the context pointer either points to the surrounding function for nested functions or to the this pointer for methods.2008/5/13 Dee Girl <deegirl noreply.com>:Yes, excellent point. The codes are equivalent. But foo is not a delegate. It is a nested function. Like in Pascal. I am not sure how it can become delegate. Maybe Walter can answer because I see he writes here. Maybe when somebody takes address of foo. Dee Girlint array[] = [1, 2, 3]; int x = 5; sort!((int a, int b) { return a + x < b + x; })(array); Two questions not one ^_^ 1. Is the code inside sort!() as powerful as delegate?The code inside sort!() /is/ a delegate. It's an anonymous delegate, but its implementation is exactly the same as a delegate with a name. It's equivalent to int foo (int a, int b) { return a + x < b + x; }; sort!(foo)(array);
May 13 2008
Dee Girl wrote:exactly what I've said.foo _is_ a delegate. there is no difference: As I explained in a different post, a delegate is simply two pointers where the context pointer either points to the surrounding function for nested functions or to the this pointer for methods.It is clear what is a delegate. It is pointer to a function and pointer to environment. Like in Lisp.But foo is not delegate. It is nested function. Like in Pascal. If you call foo from the same scope there is regular function call. Not delegate call! If you take address &foo it becomes delegate. (I just checked). D has very interesting scheme. I hope Walter writes here and confirms. But your explanation is wrong. I hope you are not offended. I am sorry, Dee Girlsure. Call it what you like, the point is that you can pass foo to a function the same way you can pass a class method. so it's a nested function that is automagically becomes a delegate by the compiler. the functionality is the same. for simplicity sake let me call it a delegate. now that we put the terminology differences aside, we both agree about the behavior of this D feature. From what I know many languages behave exactly like this (turning nested functions into delegates). side note: I heard Walter doesn't like anything that looks or smells like pascal.
May 13 2008
Yigal Chripun Wrote:Dee Girl wrote:But no language I know can pass the nested function by alias. Pass by alias is fundamental different from pass by delegate. I have tried to explain it all this time. Do we agree on this now?exactly what I've said.foo _is_ a delegate. there is no difference: As I explained in a different post, a delegate is simply two pointers where the context pointer either points to the surrounding function for nested functions or to the this pointer for methods.It is clear what is a delegate. It is pointer to a function and pointer to environment. Like in Lisp.But foo is not delegate. It is nested function. Like in Pascal. If you call foo from the same scope there is regular function call. Not delegate call! If you take address &foo it becomes delegate. (I just checked). D has very interesting scheme. I hope Walter writes here and confirms. But your explanation is wrong. I hope you are not offended. I am sorry, Dee Girlsure. Call it what you like, the point is that you can pass foo to a function the same way you can pass a class method. so it's a nested function that is automagically becomes a delegate by the compiler. the functionality is the same. for simplicity sake let me call it a delegate. now that we put the terminology differences aside, we both agree about the behavior of this D feature. From what I know many languages behave exactly like this (turning nested functions into delegates).side note: I heard Walter doesn't like anything that looks or smells like pascal.Then I am sure he wish Pascal did not have nested functions ^_^ PS I installed spell check for English on emacs now, at least fewer typos! Thank you, Dee Girl
May 13 2008
Dee Girl wrote:But no language I know can pass the nested function by alias. Pass by alias is fundamental different from pass by delegate. I have tried to explain it all this time. Do we agree on this now?the main thing for me is the syntax: look at Nemerle, for example. Here's a snippet: class MoreFunctions { static run_twice (f : int -> int, v : int) : int { f (f (v)) } static run_adder (x : int) : void { def f (y : int) : int { x + y }; // <==== this is a nested function System.Console.WriteLine ("{0}", run_twice (f, 1)) } public static Run () : void { run_adder (1); run_adder (2); } } look at: http://nemerle.org/Grok_Functionals for a better explanation.side note: I heard Walter doesn't like anything that looks or smells like pascal.Then I am sure he wish Pascal did not have nested functions ^_^ PS I installed spell check for English on emacs now, at least fewer typos! Thank you, Dee Girl
May 13 2008
2008/5/13 Yigal Chripun <yigal100 gmail.com>:the main thing for me is the syntax: look at Nemerle, for example. Here's a snippet: class MoreFunctions { static run_twice (f : int -> int, v : int) : int { f (f (v)) } static run_adder (x : int) : void { def f (y : int) : int { x + y }; // <==== this is a nested function System.Console.WriteLine ("{0}", run_twice (f, 1)) } public static Run () : void { run_adder (1); run_adder (2); } }And the same thing in D: class MoreFunctions { static run_twice!(alias f)(int v) { f(f(v)) } static run_adder (x : int) : void { int f(int y) { return x + y; }; writefln("{0}", run_twice!(f)(1)) } void main() { run_adder(1); run_adder(2); } } What's your point?
May 13 2008
Janice Caron wrote:2008/5/13 Yigal Chripun <yigal100 gmail.com>:exactly. the point was for Dee about other languages that provide the same functionality.the main thing for me is the syntax: look at Nemerle, for example. Here's a snippet: class MoreFunctions { static run_twice (f : int -> int, v : int) : int { f (f (v)) } static run_adder (x : int) : void { def f (y : int) : int { x + y }; // <==== this is a nested function System.Console.WriteLine ("{0}", run_twice (f, 1)) } public static Run () : void { run_adder (1); run_adder (2); } }And the same thing in D: class MoreFunctions { static run_twice!(alias f)(int v) { f(f(v)) } static run_adder (x : int) : void { int f(int y) { return x + y; }; writefln("{0}", run_twice!(f)(1)) } void main() { run_adder(1); run_adder(2); } } What's your point?
May 13 2008
Yigal Chripun Wrote:Janice Caron wrote:I do not know Nemerle. But the way run_twice looks it is not parameterized statically. So all calls to run_twice will pass a pointer to function and go to the same body. Many languages do it. It is very different from D run_twice which is parameterized statically. There is big difference. Also you said I talk about inlining. No. Janice is right. I talk about instantiation. I write too much but I think I can not explain things well. Maybe better I stop in this discussion. Sorry, Dee Girl2008/5/13 Yigal Chripun <yigal100 gmail.com>:exactly. the point was for Dee about other languages that provide the same functionality.the main thing for me is the syntax: look at Nemerle, for example. Here's a snippet: class MoreFunctions { static run_twice (f : int -> int, v : int) : int { f (f (v)) } static run_adder (x : int) : void { def f (y : int) : int { x + y }; // <==== this is a nested function System.Console.WriteLine ("{0}", run_twice (f, 1)) } public static Run () : void { run_adder (1); run_adder (2); } }And the same thing in D: class MoreFunctions { static run_twice!(alias f)(int v) { f(f(v)) } static run_adder (x : int) : void { int f(int y) { return x + y; }; writefln("{0}", run_twice!(f)(1)) } void main() { run_adder(1); run_adder(2); } } What's your point?
May 13 2008
2008/5/13 Janice Caron <caron800 googlemail.com>:static run_adder (x : int) : voidDarn copy/paste errors. I meant of course static void run_adder(int x)
May 13 2008
On Tue, 13 May 2008 11:59:16 +0400, Yigal Chripun <yigal100 gmail.com> wrote:foo _is_ a delegate. there is no difference: As I explained in a different post, a delegate is simply two pointers where the context pointer either points to the surrounding function for nested functions or to the this pointer for methods.No, it is not. It's just a function, that takes additional pointer-to-environment passed as a first (hidden) parameter (just like `this` inside member functions). Note that you can't call it outside of the scope (since there would be no context pointer) unless you make delegate out of it (taking its address). That way, scope pointer stored in a delegate as well. That's all.
May 13 2008
2008/5/13 Koroskin Denis <2korden gmail.com>:I'm sure Yigal intended to mean "&foo _is_ a delegate". I understood him, anyway.foo _is_ a delegate.No, it is not. It's just a function, that takes additional pointer-to-environment passed as a first (hidden) parameter
May 13 2008
Janice Caron wrote:2008/5/13 Koroskin Denis <2korden gmail.com>::-)I'm sure Yigal intended to mean "&foo _is_ a delegate". I understood him, anyway.foo _is_ a delegate.No, it is not. It's just a function, that takes additional pointer-to-environment passed as a first (hidden) parameter
May 13 2008
Yigal Chripun Wrote:my issues with the use of this syntax: a) what if I had a typo?algorithm.d(2528): Error: binaryFun(ElementType1,ElementType2) is not an lvalue algorithm.d(2528): static assert "void*" Maybe the static assert message should be better. What if it says: Your comparison function is invalid?what if i typed n instead of b (they are next to each other on my keyboard)?Same message is written.your solution or any other string solution cannot check this, since it is syntactically valid D code inside the string. b) if I want to compare q{a.member > b.member} there is no check that member exists, or any other type checking. the error would occur in the template code itself not in the string in both of the above examples.Same message is written again.c) what if I forgot to type the "q"?./test.d(9): found '}' when expecting ';' following 'statement' At least now it is in your file ^_^d) most IDEs mark strings with a different color or something like that so I treat them automatically as data and not code. ( this is a minor issue, since IDEs could be made to work differently. but, my sub-conscience response to strings as "not code" is harder to fix)The strings are short otherwise they anyway are better as functions. I do not think this could be a problem.the only benefit to this is a slight performance benefit (though a good compiler could provide the same performance by inlining a function call) and a slightly shorter syntax which is a personal preference since you type less but IMHO it becomes less readable and less obvious.I think it is very readable and obvious for reasons below.I prefer: sort!((int a, int b) {return a.member > b.member;})(array); this is more typing but provides all the checks and is more readable.It is nice you can do both. You can put a string when it is small. You double the size of the code to write. When you have long comparison, you make it separate function. What I think is best of D when coming from other languages is that it gives so much options in such small core.Think of the poor guy that will need to do maintenance on your code years from now. now the string is a cool quick'n'dirty solution to the problem at hand but in the future that other programmer will have no idea what those "a" and "b" are. especially since you didn't write the types of them.Here you may be wrong two times. First I tell from experience. I worked internship for a biotech company in Boston. I thought it will be research but it was so much work! ^_^ They used Perl and I did not know Perl when I began. But then I learned it and it has many nice things. One is sort which looks like this sort { $a <=> $b } array; I remember: first time I saw sort I understood how it works. The only odd thing was <=> but that means -1, 0 or 1 if a is less, equal or greater than b. I thought Perl has the most elegant definition of sort but now I think D has it. Second you may be wrong because you think types help. They do not help in fact they may un-help. Write this long array[] = ...; sort!((int a, int b) {return a > b;})(array); Sadly D lets long -> int conversion go through. Code will compile and run! But it gives wrong results for big numbers. For sort type deduction is best. You should write sort!((auto a, auto b) {return a > b;})(array); And that works with int, long, float, string and all types that have >. It is the best writing of the intent of the sort. But I do not know how much type inference the D compiler can do.also the performance could be the same if I used a function instead of a delegate allowing the compiler to inline it (hopefully).Good abstraction is the best. Performance is a good bonus ^_^since this is identical to: sort(array, (int a, int b) {return a.member > b.member;}); there is no benefit in making it a template, IMO.I hoped I convinced you otherwise. After all effort still no understanding? Templates are best. They give you static and dynamic. Dynamic only gives dynamic.side note: using a feature of arrays in D this could be also rewritten as: array.sort((int a, int b) {return a.member > b.member;});Would be nice to allow array.sort!(cmp).Again, this is only my opinion (and I do prefer higher level solutions and think the compler/linker can do a better job optimizing than I can). you do not have to agree with this view, and there are already people that disagree.Different views of same issues is good. But I see there are some things about which you are simply wrong because you do not understand how they work. Then I waste time explaining ^_^ Thank you, Dee Girl
May 12 2008
Please read my response to Bill. Dee Girl wrote:Yigal Chripun Wrote:But it doesn't. also, the file name and line number are wrong. that IS one of my problems with this.my issues with the use of this syntax: a) what if I had a typo?algorithm.d(2528): Error: binaryFun(ElementType1,ElementType2) is not an lvalue algorithm.d(2528): static assert "void*" Maybe the static assert message should be better. What if it says: Your comparison function is invalid?again, The error I get points me to the wrong place with the wrong error.what if i typed n instead of b (they are next to each other on my keyboard)?Same message is written.should I repeat the above, again?your solution or any other string solution cannot check this, since it is syntactically valid D code inside the string. b) if I want to compare q{a.member > b.member} there is no check that member exists, or any other type checking. the error would occur in the template code itself not in the string in both of the above examples.Same message is written again.oh, god! finally! (that's my inner atheist talking ;) ) and yet, I still need to type that q which is meaningless to me.c) what if I forgot to type the "q"?../test.d(9): found '}' when expecting ';' following 'statement' At least now it is in your file ^_^code is read 100 times more then it's written you need to type more "once" but I get to read it many times.d) most IDEs mark strings with a different color or something like that so I treat them automatically as data and not code. ( this is a minor issue, since IDEs could be made to work differently. but, my sub-conscience response to strings as "not code" is harder to fix)The strings are short otherwise they anyway are better as functions. I do not think this could be a problem.the only benefit to this is a slight performance benefit (though a good compiler could provide the same performance by inlining a function call) and a slightly shorter syntax which is a personal preference since you type less but IMHO it becomes less readable and less obvious.I think it is very readable and obvious for reasons below.I prefer: sort!((int a, int b) {return a.member > b.member;})(array); this is more typing but provides all the checks and is more readable.It is nice you can do both. You can put a string when it is small. You double the size of the code to write. When you have long comparison, you make it separate function. What I think is best of D when coming from other languages is that it gives so much options in such small core.personally, I dislike Perl. You think that the above example is elegant?! in what way does the above make it obvious that array is sorted in ascending order?? <=> look to me as the mathematical sign for "if and only if" (iff, i think is the short form) which doesn't mean anything regarding sort order. in what way does it make clear that the sort here is a function call applied to array? this is the opposite of readable to me.Think of the poor guy that will need to do maintenance on your code years from now. now the string is a cool quick'n'dirty solution to the problem at hand but in the future that other programmer will have no idea what those "a" and "b" are. especially since you didn't write the types of them.Here you may be wrong two times. First I tell from experience. I worked internship for a biotech company in Boston. I thought it will be research but it was so much work! ^_^ They used Perl and I did not know Perl when I began. But then I learned it and it has many nice things. One is sort which looks like this sort { $a <=> $b } array; I remember: first time I saw sort I understood how it works. The only odd thing was <=> but that means -1, 0 or 1 if a is less, equal or greater than b. I thought Perl has the most elegant definition of sort but now I think D has it.Second you may be wrong because you think types help. They do not help in fact they may un-help. Write this long array[] = ...; sort!((int a, int b) {return a > b;})(array); Sadly D lets long -> int conversion go through. Code will compile and run! But it gives wrong results for big numbers.So a bug in DMD makes me wrong? That's a compiler bug that needs to be fixed. the only thing your example here shows is that DMD has bugs. on the other hand I can give simple example where types do help: class A { int member();} class B { int member(); int value;} sort!(q{a.member > b.member})(array); if i change element type from A to B the string will compile. what if those classes are unrelated and the member functions do completely different things, that's just a coincidence they have the same name. when i changed to B i wanted to sort by value instead. but i could miss one of the calls to sort and the compiler will not yell at me with the string solution, thus i've introduced a search-and replace bug (i didn't replace all the instances, by mistake) a proper function will produce an error since the type of the elements is not the same at the elements in the array.For sort type deduction is best. You should write sort!((auto a, auto b) {return a > b;})(array); And that works with int, long, float, string and all types that haveThat's an interesting idea, I don't know how much work does it take to implement this, but it worth discussing in a different thread. also, you can define a templated delegate: bool comp!(T)(T a, T b) {return a > b;} sort!(comp)(array); however, I'd imagine that those either need to be already provided in the standard library,or there should be a default way for sorting: so, for regular ascending sort: array.sort; // no need to provide comperator. actually, I'd want sort to have another parameter to define ascending/descending sort (bool or enum or something like that). the idea is making the common case the simplest so if you just need a regular sort, just call array.sort and it'll do the right thing. you only need to actually provide a comperator if you're not doing a regular sort. and in that case, I'd prefer a function/delegate so the compiler could check that my class does have the members I'm comparing and give me the right error.. It is the best writing of the intent of the sort. But I do notknow how much type inference the D compiler can do.Agreed. "first make sure the program is correct, then make sure it's fast"also the performance could be the same if I used a function instead of a delegate allowing the compiler to inline it (hopefully).Good abstraction is the best. Performance is a good bonus ^_^take a delegate, and you'll see that both solutions are identical. if you want static, provide a function instead of a delegate. It's a standard practice to overload functions and provide a version for both. (or wrapping it in a new type that handles this internally). there is a function call here in any case, template or no template. the compiler can inline a static function call if it's suitable (short). this is possible in both ways.since this is identical to: sort(array, (int a, int b) {return a.member > b.member;}); there is no benefit in making it a template, IMO.I hoped I convinced you otherwise. After all effort still no understanding? Templates are best. They give you static and dynamic. Dynamic only gives dynamic.since there are language issues involved here (both of us are not native English speakers) I'll ignore the implied insult in that you waste time on this discussion with me (you choose to answer, you know...). I try not to insult people, please try and do the same. besides, had it occurred to you that maybe, you didn't understand what I was saying, either because of my explanation which is not clear enough, or cause you didn't understand it? there are two things we discuss here, one of which is personal preference regarding syntax. In what way am I wrong that I like typing one and not the other? it's not a right vs. wrong issue but a Yigal's preferred style vs Janice's and Dee's preferred style. I prefer mine because I like to think it's more consistent from an end-users point of view. even if it is indeed implemented with templates under the hood. with my syntax, I just use array, then a dot and than the behavior I want to get. i.e. - array.sort; //same as array.sort(ASC); array.sort(DESC); array.sort(someStaticComperator); array.sort(aDelegateComperator); array.someOtherfunction(params); //and also List.sort; someOtherCollection.sort; int.sort; //compile time error, no sort method for int etc... I know my examples are lame (Janice is much better than me in explaining stuff). I hope you see now, what I mean by a consistent API. this is what I think the end user would prefer. what is wrong with the above, in your opinion?side note: using a feature of arrays in D this could be also rewritten as: array.sort((int a, int b) {return a.member > b.member;});Would be nice to allow array.sort!(cmp).Again, this is only my opinion (and I do prefer higher level solutions and think the compler/linker can do a better job optimizing than I can). you do not have to agree with this view, and there are already people that disagree.Different views of same issues is good. But I see there are some things about which you are simply wrong because you do not understand how they work. Then I waste time explaining ^_^ Thank you, Dee Girl
May 12 2008
Yigal Chripun Wrote:Please read my response to Bill. Dee Girl wrote:Indeed it is not nice. Like in C++. But I learned to be happy with only an error. I thought errors in C++ looked bad but when I used Haskell I think C++ errors are easy ^_^Yigal Chripun Wrote:But it doesn't. also, the file name and line number are wrong. that IS one of my problems with this.my issues with the use of this syntax: a) what if I had a typo?algorithm.d(2528): Error: binaryFun(ElementType1,ElementType2) is not an lvalue algorithm.d(2528): static assert "void*" Maybe the static assert message should be better. What if it says: Your comparison function is invalid?This argument may be not correct. It is true code is read many times but if it is long then it is hard to read not only to write. In software engineering class I learned metrics that bug rate is proportional to code size in many languages.It is nice you can do both. You can put a string when it is small. You double the size of the code to write. When you have long comparison, you make it separate function. What I think is best of D when coming from other languages is that it gives so much options in such small core.code is read 100 times more then it's written you need to type more "once" but I get to read it many times.The point I make is that I found it easy to learn in practice. It is not a hypothesis. It is a story. Maybe if we worked together at the company we would both easily learned sort in perl.personally, I dislike Perl. You think that the above example is elegant?! in what way does the above make it obvious that array is sorted in ascending order?? <=> look to me as the mathematical sign for "if and only if" (iff, i think is the short form) which doesn't mean anything regarding sort order. in what way does it make clear that the sort here is a function call applied to array? this is the opposite of readable to me.Think of the poor guy that will need to do maintenance on your code years from now. now the string is a cool quick'n'dirty solution to the problem at hand but in the future that other programmer will have no idea what those "a" and "b" are. especially since you didn't write the types of them.Here you may be wrong two times. First I tell from experience. I worked internship for a biotech company in Boston. I thought it will be research but it was so much work! ^_^ They used Perl and I did not know Perl when I began. But then I learned it and it has many nice things. One is sort which looks like this sort { $a <=> $b } array; I remember: first time I saw sort I understood how it works. The only odd thing was <=> but that means -1, 0 or 1 if a is less, equal or greater than b. I thought Perl has the most elegant definition of sort but now I think D has it.I understand what you say. Then you have option to specify type if you want. But some other times (maybe more often) you prefer generic.Second you may be wrong because you think types help. They do not help in fact they may un-help. Write this long array[] = ...; sort!((int a, int b) {return a > b;})(array); Sadly D lets long -> int conversion go through. Code will compile and run! But it gives wrong results for big numbers.So a bug in DMD makes me wrong? That's a compiler bug that needs to be fixed. the only thing your example here shows is that DMD has bugs. on the other hand I can give simple example where types do help: class A { int member();} class B { int member(); int value;} sort!(q{a.member > b.member})(array); if i change element type from A to B the string will compile. what if those classes are unrelated and the member functions do completely different things, that's just a coincidence they have the same name. when i changed to B i wanted to sort by value instead. but i could miss one of the calls to sort and the compiler will not yell at me with the string solution, thus i've introduced a search-and replace bug (i didn't replace all the instances, by mistake) a proper function will produce an error since the type of the elements is not the same at the elements in the array.I looked in std.functional and this is exactly how it works ^_^For sort type deduction is best. You should write sort!((auto a, auto b) {return a > b;})(array); And that works with int, long, float, string and all types that haveThat's an interesting idea, I don't know how much work does it take to implement this, but it worth discussing in a different thread. also, you can define a templated delegate: bool comp!(T)(T a, T b) {return a > b;} sort!(comp)(array);. It is the best writing of the intent of the sort. But I do notknow how much type inference the D compiler can do.however, I'd imagine that those either need to be already provided in the standard library,or there should be a default way for sorting: so, for regular ascending sort: array.sort; // no need to provide comperator. actually, I'd want sort to have another parameter to define ascending/descending sort (bool or enum or something like that). the idea is making the common case the simplest so if you just need a regular sort, just call array.sort and it'll do the right thing.But this works with std.algorithm: sort(array). If you want to say array.sort is much better I think becomes pity (pety?) meaning minor issue. But you can not say you hate one and love the other just because this detail. And I see sort was wrote so that it is generic and works with others not only arrays. STL style. Then if another container appears then std.sort works with it. But if sort is forced to work only for arrays then it need many implementations (example for deque). If that works we can say std.sort is objectivally superior.you only need to actually provide a comperator if you're not doing a regular sort. and in that case, I'd prefer a function/delegate so the compiler could check that my class does have the members I'm comparing and give me the right error.You can use mysort. std.sort is good because it lets you do so. If you only give me mysort then it does not let others do the static way.I do not understand. Can you please write a bit of code that would be better than std.sort?Agreed. "first make sure the program is correct, then make sure it's fast"also the performance could be the same if I used a function instead of a delegate allowing the compiler to inline it (hopefully).Good abstraction is the best. Performance is a good bonus ^_^take a delegate, and you'll see that both solutions are identical. if you want static, provide a function instead of a delegate. It's a standard practice to overload functions and provide a version for both. (or wrapping it in a new type that handles this internally). there is a function call here in any case, template or no template. the compiler can inline a static function call if it's suitable (short). this is possible in both ways.since this is identical to: sort(array, (int a, int b) {return a.member > b.member;}); there is no benefit in making it a template, IMO.I hoped I convinced you otherwise. After all effort still no understanding? Templates are best. They give you static and dynamic. Dynamic only gives dynamic.side note: using a feature of arrays in D this could be also rewritten as: array.sort((int a, int b) {return a.member > b.member;});Would be nice to allow array.sort!(cmp).Again, this is only my opinion (and I do prefer higher levelI am so sorry!! I used a wrong word. It was meant as spending time on explaining instead of working on my project. Sorry!since there are language issues involved here (both of us are not native English speakers) I'll ignore the implied insult in that you waste time on this discussion with me (you choose to answer, you know...). I try not to insult people, please try and do the same.solutions and think the compler/linker can do a better job optimizing than I can). you do not have to agree with this view, and there are already people that disagree.Different views of same issues is good. But I see there are some things about which you are simply wrong because you do not understand how they work. Then I waste time explaining ^_^ Thank you, Dee Girlbesides, had it occurred to you that maybe, you didn't understand what I was saying, either because of my explanation which is not clear enough, or cause you didn't understand it?It is always possible. Preferences are always not to contradict about so I never contradict about them. But some times I see a sentence or two that I think is wrong. Then I try to explain how it can be corrected. I do not know if the thinking is wrong, but if the sentence is wrong then it can be corrected.there are two things we discuss here, one of which is personal preference regarding syntax. In what way am I wrong that I like typing one and not the other? it's not a right vs. wrong issue but a Yigal's preferred style vs Janice's and Dee's preferred style.Yes. I never wanted to say your preference is wrong. Please forgive me if I did. In things of preference maybe we agree on this. If there is serious advantage to a library then we can agree that we can accept both array.sort or sort(array).I prefer mine because I like to think it's more consistent from an end-users point of view. even if it is indeed implemented with templates under the hood. with my syntax, I just use array, then a dot and than the behavior I want to get. i.e. - array.sort; //same as array.sort(ASC);Here I may prefer array.sort("a < b") instead of array.sort(ASC). Maybe I think ASC means ASCII. But if I see the comparison it is clear instantly. But it is just preference.array.sort(DESC);Is DESC a shortcut for DESCRIPTION? ^_-array.sort(someStaticComperator); array.sort(aDelegateComperator); array.someOtherfunction(params); //and also List.sort; someOtherCollection.sort; int.sort; //compile time error, no sort method for int etc...But now again we seem out of preference domain. Maybe I misunderstand but each collection implements its own sort? Then this solution is objectivally inferior because deque.sort and array.sort are duplicated. I think STL style is superior. It implements sort for anything that has iterator and swap.I know my examples are lame (Janice is much better than me in explaining stuff). I hope you see now, what I mean by a consistent API. this is what I think the end user would prefer. what is wrong with the above, in your opinion?Subjective preferences are always good. May be not if they lead to bad design. Then if you only have a syntax preference as argument then it is too little power. If you write a design that is better than std.sort then it is perfect. I am very curious. About examples. My adviser (software engineering) tells me: if you can not show good examples for your idea then your idea has a problem. Good ideas have good examples. Thank you, Dee Girl
May 12 2008
Yigal Chripun wrote:Leandro Lucarella wrote:All of these things will cause compile-time errors. You are right that the error reported may not point to the actual mistake in a very clear way, but you will get a syntax error right away. The string gets turned into code via mixin(the_string) so if anything is not right with the code in the string, the compiler will barf. Maybe you were thinking it was doing some sort of parsing of the string to generate a comparison function for you? No. Just a raw mixin() in a scope in which 'a' and 'b' have been aliased to be the two elements being compared.Janice Caron, el 11 de mayo a las 12:53 me escribiste:this was already debated thoroughly, But I'll state my POV again. I don't like the use of strings here at all. in any form. D is a strongly typed language which I like and all those string tricks although cool are bad IMO. my issues with the use of this syntax: a) what if I had a typo? what if i typed n instead of b (they are next to each other on my keyboard)? your solution or any other string solution cannot check this, since it is syntactically valid D code inside the string. b) if I want to compare q{a.member > b.member} there is no check that member exists, or any other type checking. the error would occur in the template code itself not in the string in both of the above examples. c) what if I forgot to type the "q"?On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I think you can use Token Strings[1] in D2 to have better error reporting. It justs do lexical analisys, but it's something: sort!(q{a > b})(array); [1] http://www.digitalmars.com/d/2.0/lex.htmlalso, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere.On that point, I concede. The reporting of template errors could certainly be improved. More than once I have wanted to see the "stack trace" of errors, working back from the innermost, to the line of code that ultimately triggered it. However, that it not an argument against templates, it is an argument for improved error reporting. And hopefully, one day we'll get that. This is not the first time that you've argued that some feature or strategy is bad because today's D compiler isn't good enough, but you need to remember that tomorrow's D compiler will be better. That's life on the cutting edge.d) most IDEs mark strings with a different color or something like that so I treat them automatically as data and not code. ( this is a minor issue, since IDEs could be made to work differently. but, my sub-conscience response to strings as "not code" is harder to fix)The q{foo} thing was taken from Perl, so any IDE that can highlight perl should be able to highlight this in D with some tweaks. --bb
May 12 2008
Bill Baxter wrote:Yigal Chripun wrote:i know it's a string mixin. my personal issue with it is indeed the wrong errors. I also dislike perl. I like Smalltalk (and Ruby) though. also I'd prefer an array.sort(comp); over sort!(comp)(array); but, AGAIN, that's just MY personal preference (since I like the code blocks of Ruby/Smalltalk). it's simpler from the end user's POV.Leandro Lucarella wrote:All of these things will cause compile-time errors. You are right that the error reported may not point to the actual mistake in a very clear way, but you will get a syntax error right away. The string gets turned into code via mixin(the_string) so if anything is not right with the code in the string, the compiler will barf. Maybe you were thinking it was doing some sort of parsing of the string to generate a comparison function for you? No. Just a raw mixin() in a scope in which 'a' and 'b' have been aliased to be the two elements being compared.Janice Caron, el 11 de mayo a las 12:53 me escribiste:this was already debated thoroughly, But I'll state my POV again. I don't like the use of strings here at all. in any form. D is a strongly typed language which I like and all those string tricks although cool are bad IMO. my issues with the use of this syntax: a) what if I had a typo? what if i typed n instead of b (they are next to each other on my keyboard)? your solution or any other string solution cannot check this, since it is syntactically valid D code inside the string. b) if I want to compare q{a.member > b.member} there is no check that member exists, or any other type checking. the error would occur in the template code itself not in the string in both of the above examples. c) what if I forgot to type the "q"?On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I think you can use Token Strings[1] in D2 to have better error reporting. It justs do lexical analisys, but it's something: sort!(q{a > b})(array); [1] http://www.digitalmars.com/d/2.0/lex.htmlalso, what if a doesn't actually have a "member" member? this is still syntactically valid D code, but again I think the error would be reported elsewhere.On that point, I concede. The reporting of template errors could certainly be improved. More than once I have wanted to see the "stack trace" of errors, working back from the innermost, to the line of code that ultimately triggered it. However, that it not an argument against templates, it is an argument for improved error reporting. And hopefully, one day we'll get that. This is not the first time that you've argued that some feature or strategy is bad because today's D compiler isn't good enough, but you need to remember that tomorrow's D compiler will be better. That's life on the cutting edge.d) most IDEs mark strings with a different color or something like that so I treat them automatically as data and not code. ( this is a minor issue, since IDEs could be made to work differently. but, my sub-conscience response to strings as "not code" is harder to fix)The q{foo} thing was taken from Perl, so any IDE that can highlight perl should be able to highlight this in D with some tweaks. --bb
May 12 2008
Yigal Chripun escribió:my issues with the use of this syntax: a) what if I had a typo? what if i typed n instead of b (they are next to each other on my keyboard)? your solution or any other string solution cannot check this, since it is syntactically valid D code inside the string. b) if I want to compare q{a.member > b.member} there is no check that member exists, or any other type checking. the error would occur in the template code itself not in the string in both of the above examples. c) what if I forgot to type the "q"? d) most IDEs mark strings with a different color or something like that so I treat them automatically as data and not code. ( this is a minor issue, since IDEs could be made to work differently. but, my sub-conscience response to strings as "not code" is harder to fix)I mostly agree with you. The problem with defining these things is strings is that... they are strings! They are not bind to symbols in the language. So in that sense you can't expect any help from an IDE: no autocompletion, no refactor, no go to definition, no nothing. can see the designers take careful steps to make the language good, eficcient, and IDE-enabled (making an IDE for those languages is relatively easy). defined in a static class, and the first parameter must have a "this". In that way, an IDE must only search in static classes, and in those, just the methods that have a "this". In D, if there are no restrictions, you could potentially need to make a big search in order to see where an extension method can or cannot be proposed.
May 12 2008
Ary Borenszweig wrote:I mostly agree with you. The problem with defining these things is strings is that... they are strings! They are not bind to symbols in the language. So in that sense you can't expect any help from an IDE: no autocompletion, no refactor, no go to definition, no nothing. can see the designers take careful steps to make the language good, eficcient, and IDE-enabled (making an IDE for those languages is relatively easy). defined in a static class, and the first parameter must have a "this". In that way, an IDE must only search in static classes, and in those, just the methods that have a "this". In D, if there are no restrictions, you could potentially need to make a big search in order to see where an extension method can or cannot be proposed.Good Points! I thought about the IDE issue, but I wasn't sure how the IDE handles this.
May 12 2008
2008/5/13 Yigal Chripun <yigal100 gmail.com>:Good Points! I thought about the IDE issue, but I wasn't sure how the IDE handles this.I don't have an IDE, but in my text editor, "a < b" is colored like a string, but q{ a < b } is colored like D code throughout. So I can choose my quoting method depending on how I want my code colored. This works, not because my IDE is smart, but precisely because it isn't! It doesn't realise that q{...} is a string, so it gets colored like code.
May 13 2008
Janice Caron wrote:2008/5/13 Yigal Chripun <yigal100 gmail.com>:OK, but the point Ary made was that an IDE is smart and provides auto-complete and similar features which cannot be provided for strings.Good Points! I thought about the IDE issue, but I wasn't sure how the IDE handles this.I don't have an IDE, but in my text editor, "a < b" is colored like a string, but q{ a < b } is colored like D code throughout. So I can choose my quoting method depending on how I want my code colored. This works, not because my IDE is smart, but precisely because it isn't! It doesn't realise that q{...} is a string, so it gets colored like code.
May 13 2008
Janice Caron escribió:2008/5/13 Yigal Chripun <yigal100 gmail.com>:That's just because normally most editors are configured so that "" and '' are colored as strings, so Walter chose an uncommon combination that won't get colored. But Descent colors q{...} as a string because it understands D and because... because it *is* a string! :-) Also, same comment as Yigal: the coloring is the least important feature in my previous post.Good Points! I thought about the IDE issue, but I wasn't sure how the IDE handles this.I don't have an IDE, but in my text editor, "a < b" is colored like a string, but q{ a < b } is colored like D code throughout. So I can choose my quoting method depending on how I want my code colored. This works, not because my IDE is smart, but precisely because it isn't! It doesn't realise that q{...} is a string, so it gets colored like code.
May 13 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:it mis-uses templates, as I said in my previous post. it's the same C++ mind set. the only difference is that it utilizes more D++ features to accomplish the same thing. Personally, I prefer a smalltalk like collections framework over the c++ generic templates solution. it makes so much more sense to me to have a orderedCollection interface which defines a sort method, than a c++ generic sort template. It makes more sense to me to write: array.sort(); or array.sort(aDelegate); instead of using: sort(array); or sort(array, aDelegate); or even worse: sort!(aDelegate)(array); C++ avoids using real OOP encapsulation since it perceives it as adding unnecessary overhead. this is plain wrong since the overhead is orthogonal to the design principles. This is why C++ is adding the notion of concepts (which facilitate compile time OOP) to the next standard. --YigalBUT, the main difference is that for C++ this is probably the only reasonable solution as C++ doesn't have delegates.C++ has functors (classes which overload operator ()), and they're a bit like delgates. What C++ doesn't have is alias template parameters, nor string template parameters. sort!("a.member > b.member")(array) simply couldn't be done in C++. This is /not/ a C++ solution. It's a D solution, and a fantastically brilliant one. Perhaps what you mean when you say "C++ solution", is, "it uses templates"?
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:Personally, I prefer <snip>By your own words, that's just a personal preference.It makes more sense to me to write: array.sort(); or array.sort(aDelegate); instead of using: sort(array); or sort(array, aDelegate); or even worse: sort!(aDelegate)(array);But now you're just saying "my syntax is better than your syntax", when both do the same thing. In addition, the D version is more powerful, as it allows you to say: sort!(f)(array) where f is a function (not a delegate). It also allows you to write sort!(dg, SwapStrategy.stable)(array) to get a stable sort instead of a non-stable (but faster) sort. This kind of flexibility offers way more power than delegates alone. Besides which, arrays cannot implement an orderedCollection interface, because arrays cannot implement interfaces, so you'd be limited only to sorting custom collection classes.This is why C++ is adding the notion of concepts (which facilitate compile time OOP)So is D. Shall we take bets on which language gets them first, or in which language they will be most powerful?
May 11 2008
Janice Caron wrote:Is it? If so that's great. Last comment I recall from Walter on the subject was that he believed D already had something as good as concepts. I can't recall what it was exactly -- IIRC he said something like D doesn't need concepts because it has "static if". I can't seem to find the thread where he said that, though. --bbThis is why C++ is adding the notion of concepts (which facilitate compile time OOP)So is D. Shall we take bets on which language gets them first, or in which language they will be most powerful?
May 11 2008
you miss the point. it's not just a matter of syntax it's a matter of better design strategy. yes, the syntax is a personal preference, but that preference is used to emphasize the design goal of proper encapsulation. Also, the template solution has the same power and all those examples you gave can be accomplished in both ways. nothing stops me from using: array.sort(SwapStrategy.stable, dg); also you can use functions instead of delegates too (although, I really don't see what's the point of that besides maybe some questionable performance issues) D already has a feature to allow me to define a free function that takes an array as first parameter, and use it as array.Fn(other params); this notion will extended in D2. so the issue here is hardy the syntax itself (as the two forms are interchangeable). Also I do realize that since arrays are builtins they cannot implement interfaces. IMO, the best solution is to make all type behave like objects via syntax sugar. D is already half way there, since you can do int.max and such. My point is that I separate the interface exposed to the user ( the syntax of D) from the implementation. in order to make "hello world" or 34 behave like objects you don't have to add overhead. 34 can remain the same int it is in C. you just need to add to the compiler the ability to treat those as objects, that's the whole beauty of a compiled language, you can perform some transformations by the compiler so that numbers and strings and other builtins will behave like objects without the added cost you get in a language like Ruby for that functionality. My mantra is: use the right tool for the job. That means that although templates are a useful tool, I don't think it's the only tool D provides that should be used to solve all problems. IMO, this problem is better solved with polymorphism and proper encapsulation. --Yigal
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:you miss the point.I may be missing /a/ point, but I doubt I'm missing /the/ point. Who gets to decide what /the/ point is anyway? Is there even a "the" point to begin with?it's not just a matter of syntax it's a matter of better design strategy.Who gets to define "better"? Seems to me that Phobos has the better design strategy, but that's just personal opinion, with no more to back it up than yours. But still - the template strategy seems to offer /everything/ that polymorphism offers (including polymorphism), and more.Nothing stops me from using: array.sort(SwapStrategy.stable, dg);Except that your interface must expose that function signature, which in turn means that all implementors of that interface must support that function. Interfaces cannot provide implementations, so although you could supply a mixin, there is no guarantee that all implementors will use it.Also I do realize that since arrays are builtins they cannot implement interfaces. IMO, the best solution is to make all type behave like objects via syntax sugar. D is already half way there, since you can do int.max and such.OK, so you propose adding new features to the language, just so that you don't have to use templates? The prevailing wisdom is, if it can be done with a library solution, it should; if not, then maybe we need new syntax. But we don't generally get new syntax just because some people don't like templates, so I suspect that that is unlikely to happen.My point is that I separate the interface exposed to the user ( the syntax of D) from the implementation.So does std.algorithm. std.algorithm provides container-independent, sort-criterion-independent, algorithms. Plug in container or sort-criterion of your choice. The container only needs to expose iterators, and all of std.algorithm's functions will work.My mantra is: use the right tool for the job. That means that although templates are a useful tool, I don't think it's the only tool D provides that should be used to solve all problems. IMO, this problem is better solved with polymorphism and proper encapsulation.Again, who gets to define "better"?
May 11 2008
See my comments bellow. Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I want to be able to do this: List!(T) col = new LinkedList!(T); and be able to change the linked list to a vector in the future (or any other suitable collection) with the changes limited to this line alone. is it possible with templates? c++ certainly does not provide this flexibility with STL.you miss the point.I may be missing /a/ point, but I doubt I'm missing /the/ point. Who gets to decide what /the/ point is anyway? Is there even a "the" point to begin with?it's not just a matter of syntax it's a matter of better design strategy.Who gets to define "better"? Seems to me that Phobos has the better design strategy, but that's just personal opinion, with no more to back it up than yours. But still - the template strategy seems to offer /everything/ that polymorphism offers (including polymorphism), and more.huh? the point of interfaces is to define relations so that I can be sure that I can sort all sortable collections for instance. the fact that it's in the interface dictates that all derived classes have to provide some way to do that. I don't care as a user how that is implemented, I just know I can sort my sortable collection since it's in the interface. another benefit is that each derived class can provide a specialized and optimized version for its own internal structure. Iterators add overhead here for no reason. oh there is one reason, generality... right?Nothing stops me from using: array.sort(SwapStrategy.stable, dg);Except that your interface must expose that function signature, which in turn means that all implementors of that interface must support that function. Interfaces cannot provide implementations, so although you could supply a mixin, there is no guarantee that all implementors will use it.actually, I'm talking about existing D features and Walter's plans to enhance them which where announced in last year's conference. I'm merely stating that those changes would be useful here, I didn't propose anything new here.Also I do realize that since arrays are builtins they cannot implement interfaces. IMO, the best solution is to make all type behave like objects via syntax sugar. D is already half way there, since you can do int.max and such.OK, so you propose adding new features to the language, just so that you don't have to use templates? The prevailing wisdom is, if it can be done with a library solution, it should; if not, then maybe we need new syntax. But we don't generally get new syntax just because some people don't like templates, so I suspect that that is unlikely to happen.again, Iterators can be useful for some tasks, but they are inherently limited. I've read an article once about that topic, if I can find it, I'll post it here. the gist is that there are more general and better solutions than iterators. C++ uses iterators extensively simply because it has no other choice since it lacks the needed language support for other approaches. fortunately, D does provide all the necessary language support, so we are not limited by iterators any more. :)My point is that I separate the interface exposed to the user ( the syntax of D) from the implementation.So does std.algorithm. std.algorithm provides container-independent, sort-criterion-independent, algorithms. Plug in container or sort-criterion of your choice. The container only needs to expose iterators, and all of std.algorithm's functions will work.My mantra is: use the right tool for the job. That means that although templates are a useful tool, I don't think it's the only tool D provides that should be used to solve all problems. IMO, this problem is better solved with polymorphism and proper encapsulation.Again, who gets to define "better"?
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I want to be able to do this: List!(T) col = new LinkedList!(T); and be able to change the linked list to a vector in the future (or any other suitable collection) with the changes limited to this line alone. is it possible with templates?Umm. Yes. auto col = new LinkedList!(T); sort(col); becomes auto col = new Vector!(T); sort(col);huh? the point of interfaces is to define relations so that I can be sure that I can sort all sortable collections for instance. the fact that it's in the interface dictates that all derived classes have to provide some way to do that. I don't care as a user how that is implemented, I just know I can sort my sortable collection since it's in the interface.But ... what if it isn't? What if I import some.other.Collection which doesn't support your interface? All of a sudden, my ability to sort vanishes. You can argue that that is entirely the fault of some.other.Collection, for not implementing the interface, but come on! If I want to create a collection, frankly, I just want to implement the ability to put things in and get things out and store things efficiently. I don't want to have to muck about re-inventing the wheel by implementing stable sort, over and and over again for each new collection.another benefit is that each derived class can provide a specialized and optimized version for its own internal structure. Iterators add overhead hereNo they don't. All they do is expose the ability to access the collection, which you need to have anyway.for no reason. oh there is one reason, generality... right?There are several reasons. Buglessness would be a good one, in my eyes. I know that if I do sort(whatever) using std.algorithm, then there will be no bugs, even if "whatever" is a third-party collection. (That's assuming of course that std.algorithm.sort has been thoroughly debugged, but with a standard library one would certainly expect that). Whereas, if some third-party-collection has reinvented the wheel and implemented their own sort ... again ... well, that's just one more place for bugs to creep in. And of course, it's not just sort(). std.algorithm also provides functions like reduce(), filter(), find(), count(), and a gazillion more, all highly customizable. You want all of those in your container interface, so that container implementors have to implement all of them every time? Frankly, it would be irresponsibility of a standard library /not/ to provide reusable implementations.fortunately, D does provide all the necessary language support, so we are not limited by iterators any more.Your use of language is prejudicial here. Note that I can substitute anything I don't like into that sentence. For example: "Fortunately, D does provide all the necessary language support, so we are not limited by foreach any more", or "Fortunately, D does provide all the necessary language support, so we are not limited by polymorphism any more", or... We are not limited, period. We have choices. Why not just say that?
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:right... and what about my function? void WalkListAndDoSomething(List!(T) list); would become what? void WalkListAndDoSomething(auto list); I wonder if the compiler will accept that.. There is no such thing as List!(T) in the STL design. either you use a vector or you use a linked list. there's nothing in between.I want to be able to do this: List!(T) col = new LinkedList!(T); and be able to change the linked list to a vector in the future (or any other suitable collection) with the changes limited to this line alone. is it possible with templates?Umm. Yes. auto col = new LinkedList!(T); sort(col); becomes auto col = new Vector!(T); sort(col);But you are re-inventing the wheel! the entire point is to have a collection framework in the standard library. No one needs to write a in that much of a need of a different specialized collection. but than again, if you are writing a new specialized version instead of using the standard version that comes with the standard library than you would want to implement your highly optimized sort function as well.huh? the point of interfaces is to define relations so that I can be sure that I can sort all sortable collections for instance. the fact that it's in the interface dictates that all derived classes have to provide some way to do that. I don't care as a user how that is implemented, I just know I can sort my sortable collection since it's in the interface.But ... what if it isn't? What if I import some.other.Collection which doesn't support your interface? All of a sudden, my ability to sort vanishes. You can argue that that is entirely the fault of some.other.Collection, for not implementing the interface, but come on! If I want to create a collection, frankly, I just want to implement the ability to put things in and get things out and store things efficiently. I don't want to have to muck about re-inventing the wheel by implementing stable sort, over and and over again for each new collection.the overhead here is a separate object, and function calls for the iterator object. for most simple traversals, that's an unnecessary overhead when I could just use the collection.each method with a delegate like I can do in Ruby/Smalltalk/other high level languages than don't force me to manually advance an iterator to the next object in the collection. (yes, D does that for me with foreach, yet it is another level of indirectness) Iterators are useful when I really want to control myself the order of traversal or stuff like that. if I just want to sum the values of all the objects in the collection, than an iterator is an overhead. guess what kind of action is more common.another benefit is that each derived class can provide a specialized and optimized version for its own internal structure. Iterators add overhead hereNo they don't. All they do is expose the ability to access the collection, which you need to have anyway.nothing has to be either/or. D provides many mechanisms such as mixins for example that could be used. and the standard library can provide general functions too. if I use the container in the standard library, I can use the built-in collection.sort(); and if i use a third-party collection, i could also use thirdPartyCollection.sort(); [that will use the template solution and the proposed D2 extension for open classes, I've mentioned a few times] or if you prefer, sort(thirdPartyCollection); besides, all those templates could double-up as code the collection writers can mix-in into their collection classes to implement the interfaces. this way, a third party vendor can decide to be compatible to the D standard library and implement all those interfaces with the provided mixins, and in case his collection isn't compatible the user could use the same templates himself. that's a win-win situation.for no reason. oh there is one reason, generality... right?There are several reasons. Buglessness would be a good one, in my eyes. I know that if I do sort(whatever) using std.algorithm, then there will be no bugs, even if "whatever" is a third-party collection. (That's assuming of course that std.algorithm.sort has been thoroughly debugged, but with a standard library one would certainly expect that). Whereas, if some third-party-collection has reinvented the wheel and implemented their own sort ... again ... well, that's just one more place for bugs to creep in. And of course, it's not just sort(). std.algorithm also provides functions like reduce(), filter(), find(), count(), and a gazillion more, all highly customizable. You want all of those in your container interface, so that container implementors have to implement all of them every time? Frankly, it would be irresponsibility of a standard library /not/ to provide reusable implementations.I just did, didn't I? the point was comaring to C++ where those choices aren't available or if they are they are much harder to accomplish.fortunately, D does provide all the necessary language support, so we are not limited by iterators any more.Your use of language is prejudicial here. Note that I can substitute anything I don't like into that sentence. For example: "Fortunately, D does provide all the necessary language support, so we are not limited by foreach any more", or "Fortunately, D does provide all the necessary language support, so we are not limited by polymorphism any more", or... We are not limited, period. We have choices. Why not just say that?
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:List!(T) list; map!(doSomething)(list); or List!(T) list; reduce!(doSomething)(x,list); or List!(T) list; inPlace!(doSomething)(list); depending on which was more appropriate for doSomething.Umm. Yes.> > auto col = new LinkedList!(T); > sort(col); > > becomes > > auto col = new Vector!(T); > sort(col); > right... and what about my function? void WalkListAndDoSomething(List!(T) list); would become what?void WalkListAndDoSomething(auto list); I wonder if the compiler will accept that..I'm baffled as to why you even said that. You know it won't. It's silly.There is no such thing as List!(T) in the STL design. either you use a vector or you use a linked list. there's nothing in between.I thought we were talking D here? I took your use of "List!(T)" there to mean any arbitrary collection.But you are re-inventing the wheel!No I'm not.the entire point isAgain, you're telling me there's this "the" point, which is different from "a" point, and somehow more important than any point I might make.to have a collection framework in the standard library.Again, you're criticising today's D, and failing to acknowledge that tomorrow's D may (and almost certainly will) have a collection framework in the standard library.the overhead [of iterators] is a separate object, and function calls for the iterator object.By "object" you presumably mean struct - often times small enough to pass around in a register. There is essentially no difference between a struct containing a pointer, and a pointer. By "function" you presumably mean static function, which takes zero space in the struct, and will almost certainly be inlined when compiled. Overhead gone.for most simple traversals, that's an unnecessary overhead when I could just use the collection.each method with a delegateNow that /does/ have a function call overhead.if I just want to sum the values of all the objects in the collection, than an iterator is an overhead.Or you could just do int sum = reduce!("a+b")(0,collection); :-)besides, all those templates could double-up as code the collection writers can mix-in into their collection classes to implement the interfaces.Then aren't we lucky they exist! :-)that's a win-win situation.Yes.
May 11 2008
On Sun, 11 May 2008 19:47:14 +0400, Yigal Chripun <yigal100 gmail.com> = wrote:Janice Caron wrote:=On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I want to be able to do this: List!(T) col =3D new LinkedList!(T); and be able to change the linked list to a vector in the future (or=any other suitable collection) with the changes limited to this line =Well, yes, is possible. Even with STL. A typical trick I often do is this: typedef std::vector<Item*> Items; Items items; items.pushBack(new Item()); Item* item =3D items.front(); Items::iterator it =3D std::find(items.begin(), items.end(), someItem); if (it !=3D items.end()) { items.erase(it); } std::sort(items.begin(), items.end()); // etc And now if I decide to exchange vector with list or deque or anything el= se, all I need to do is to change a single line: typedef std::list<Item*> Items; I don't say that it is the best way to stick with, but it usually works.=alone. is it possible with templates?ight... and what about my function? void WalkListAndDoSomething(List!(T) list);Use templates. Or use polymorhism, if you expect containers to implement= = some generic interface.
May 11 2008
Koroskin Denis wrote:On Sun, 11 May 2008 19:47:14 +0400, Yigal Chripun <yigal100 gmail.com> wrote:all I can say is yuk. i can use tricks like that, or i can just use D's interface. I personally prefer the later. thanks for the info, though.Janice Caron wrote:Well, yes, is possible. Even with STL. A typical trick I often do is this: typedef std::vector<Item*> Items; Items items; items.pushBack(new Item()); Item* item = items.front(); Items::iterator it = std::find(items.begin(), items.end(), someItem); if (it != items.end()) { items.erase(it); } std::sort(items.begin(), items.end()); // etc And now if I decide to exchange vector with list or deque or anything else, all I need to do is to change a single line: typedef std::list<Item*> Items; I don't say that it is the best way to stick with, but it usually works.On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I want to be able to do this: List!(T) col = new LinkedList!(T); and be able to change the linked list to a vector in the future (or any other suitable collection) with the changes limited to this line alone. is it possible with templates?ight... and what about my function? void WalkListAndDoSomething(List!(T) list);Use templates. Or use polymorhism, if you expect containers to implement some generic interface.
May 11 2008
Koroskin Denis wrote:Use templates. Or use polymorhism, if you expect containers to implement some generic interface.Problem with using interfaces or base classes is, you can't do the following with interfaces, and you can't do it efficiently with base classes: interface Collection(T) { void addRange(U : T)(Collection!(U) collection); } While having an abstract class Collection that defines that template would make the end users a bit happier, it'd basically boil down to: void addRange(U : T)(Collection!(U) collection) { this.reserve(collection.length); foreach (u; collection) this.add(u); }
May 11 2008
Janice Caron Wrote:sort!("a.member > b.member")(array) simply couldn't be done in C++. This is /not/ a C++ solution. It's a D solution, and a fantastically brilliant one.does it have any use? Maybe implementing LINQ will be much more powerful and readable?
May 11 2008
Yigal Chripun Wrote:Your example does use delegates. the difference is this: say you have two delegates you pass to sort, for example you sort one array in ascending order, and another in descending order. the template solution will generate two almost identical sort functions with the only difference that one sort instance calls internally the first delegate, and the second instance calls the second. you get 4 functions. without the template, you get one sort function and two delegates, so only 3 functions.This should be good for the standard library. It is the most flexible approach. You can always do what you want in one line: void mysort(int array[], bool delegate comp(int, int)) { sort!(comp)(array); } Now you can call mysort many times and there is only one version of std.sort (if I understand it correctly). But think what if your mysort is the only implementation given in the standard library. Maybe sometimes it is too slow for your program. What do you do? void fastsort!(alias comp)(int[] array) { bool dg(int a, int b) { return comp(a, b); } mysort(array, dg); } This will not be faster at all ^_^. As a friend said: You can build a dynamic library from a static one but not inverse. Things only stack one direction. I think it is good the way sort is now in the standard library, it offers the maximum possible and opens all options. It is very impressive for someone coming from many other language.Now, when you use a string with the template, the sort will contain the code itself inline to the sort instance so it "saves" you a function call. IMO, the string solution is not clean ( C MACROs? )and while for simple cases it could save you a function call it's premature optimization IMO.To me it looks very convinent!Also, if I use delegates, I can't see how a template can save that function call. so it just bloats my executable with unneeded copies of sort.You can use mysort above. It is my first contribution to D. ^_^I hate when library code forces me to this. I think the programmer knows his code better than the library writer and should be able to choose himself whether he needs to use a compile time solution to save function calls or a run-time solution to prevent unnecessary executable bloat. this optimization should be made by the programmer and _not_ the library writer. This solution does not give me that freedom.It looks like you understand the exact oposite. std.sort gives you all freedom, anything else would give less. I have studied many languages but none offers the same flexibility. Functional languages force you to copy. Java, Smalltalk function and write the comparison even simple as a.member < b.member! D is the best and std.algorithm very elegant.this solution is a classic STL piece of code, BUT, the main difference is that for C++ this is probably the only reasonable solution as C++ doesn't have delegates. this is why when coding for D I urge people to rethink their solution instead of just doing what is best in C++ (or any other preferred language of the library writer). This is a prime example why the C++ solution is wrong for D.The other rant you wrote and the interesting discussion with Janice are good to read. But there is little objective points you bring. I see you do not like anything like C++ and then you go back and try to make arguments. One objective point is you like a container hierarchy. I have worked with them and they can be useful. But like with mysort the same argument works. You can build a hierarchy of containers on top of fast containers. But not the inverse. Dee Girl
May 11 2008
Dee Girl wrote:Yigal Chripun Wrote: This should be good for the standard library. It is the most flexible approach. You can always do what you want in one line: void mysort(int array[], bool delegate comp(int, int)) { sort!(comp)(array); } Now you can call mysort many times and there is only one version of std.sort (if I understand it correctly).not exactly. there's always a balance between memory and time, if you improve the first second worsens. for each different comp function there will be a different sort instance created by the compiler. a simple example: struct num(N) { int number = N; } num!(1) is different from num!(2); so with this method you'll get a bigger executable that contains the sort code twice. with my way on the other hand you'll always have only one sort function. the cost in my case is that sort will always call the comp function to compare while the template can avoid that function call. simply time vs. memory. memory is cheap nowadays and the linker could be made better to try to minimize the memory footprint. on the other hand cpu chips today come with multi-cores and the compiler could optimize many calls to small functions. so in the obj file, the compiler can inline the function body instead of calling it. the main question would be: what are you optimizing for? if you develop on a cellphone memory could be more important then time, but on a missile a nano second could mean the missile missed its target. so it all depends on what you want to achieve.But think what if your mysort is the only implementation given in the standard library. Maybe sometimes it is too slow for your program. What do you do? void fastsort!(alias comp)(int[] array) { bool dg(int a, int b) { return comp(a, b); } mysort(array, dg); } This will not be faster at all ^_^. As a friend said: You can build a dynamic library from a static one but not inverse. Things only stack one direction. I think it is good the way sort is now in the standard library, it offers the maximum possible and opens all options. It is very impressive for someone coming from many other language.My rant emphasizes specific things, since that's the point of a rant. I could talk about all the things I like in C++ and Janice would agree and we won't have anything interesting to talk about. I do use c++ and it is a powerful language. My point is not that I'm against templates, or that I hate C++. What i'm trying to say is that using any one tool for everything is wrong. I'm not arguing that delegates should replace all template code in D! I'm arguing that both solutions need to be evaluated and the better one chosen, also note that these two options overlap sometimes and (when it's suitable) both solutions should be provided so the user could choose his preferred trade-off (time vs. memory). --YigalNow, when you use a string with the template, the sort will contain the code itself inline to the sort instance so it "saves" you a function call. IMO, the string solution is not clean ( C MACROs? )and while for simple cases it could save you a function call it's premature optimization IMO.To me it looks very convinent!Also, if I use delegates, I can't see how a template can save that function call. so it just bloats my executable with unneeded copies of sort.You can use mysort above. It is my first contribution to D. ^_^I hate when library code forces me to this. I think the programmer knows his code better than the library writer and should be able to choose himself whether he needs to use a compile time solution to save function calls or a run-time solution to prevent unnecessary executable bloat. this optimization should be made by the programmer and _not_ the library writer. This solution does not give me that freedom.It looks like you understand the exact oposite. std.sort gives you all freedom, anything else would give less. I have studied many languages but none offers the same flexibility. Functional languages comparisons. C++ force you to go outside your function and write the comparison even simple as a.member < b.member! D is the best and std.algorithm very elegant.this solution is a classic STL piece of code, BUT, the main difference is that for C++ this is probably the only reasonable solution as C++ doesn't have delegates. this is why when coding for D I urge people to rethink their solution instead of just doing what is best in C++ (or any other preferred language of the library writer). This is a prime example why the C++ solution is wrong for D.The other rant you wrote and the interesting discussion with Janice are good to read. But there is little objective points you bring. I see you do not like anything like C++ and then you go back and try to make arguments. One objective point is you like a container hierarchy. I have worked with them and they can be useful. But like with mysort the same argument works. You can build a hierarchy of containers on top of fast containers. But not the inverse. Dee Girl
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:for each different comp function there will be a different sort instance created by the compiler.But surely, every container must itself be templatised on the element? I mean, unless you go the Java way and /only/ have a container for Objects. So your plan would instantiate at least one sort function for every element type. Moreover, since your OrderedCollection interface would demand that every container must implement sort, then every different kind of collection would have it's own sort function. So under your scheme, the total number of sort functions would equal the total number of container types. In other word, if your program used List!(char) list1, list2; List!(int) list3; List!(C) list4, list5; Array!(double) array1; then there would be four sort functions. In what way is that fewer than would be produced by sort(list1); sort(list2); sort(list3); sort(list4); sort(list5); sort(array1); ? The way I see it, we both get four. However, if choose /not/ to sort list3, then I only get three instantiations, but you still get four.What i'm trying to say is that using any one tool for everything is wrong.You should have just said that to start with, because it's a statement of the obvious and no one will disagree with you. :-)I'm not arguing that delegates should replace all template code in D! I'm arguing that both solutions need to be evaluated and the better one chosenWhy are you assuming that that didn't happen?also note that these two options overlap sometimes and (when it's suitable) both solutions should be provided so the user could choose his preferred trade-off (time vs. memory).Except that, if you can trivially create one from the other, then providing one is effectively providing both anyway.
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:From what I know, an efficient STL implementation would use C style containers with void* under the hood to minimize executable bloat. in a perfect world, my idealized container collection would do something similar. the interfaces need to use templates so that I have a list!(T) and such with a proper hierarchy, however under the hood all those List!(T) with different T types will use the same code. also, My idealized collection framework would use mixins extensively to share common code. so, if all those tools are combined properly, the amount of sort instances should be minimal. the Java way is the extreme opposite of the c++ way, I want something in the middle, Eiffel has the best design IMO, couple that API with C efficiency and we got a winner :) the idea is of course take the best from each language and combine it to something even better. BTW, you really should take a look at Eiffel. for example, it has multiple-inheritance implemented in a completely different way than C++ avoiding most of c++ problems with it. they have many novel ideas there that D can and should learn from. My perfect collection framework would be based on the Eiffel design, avoiding problems in the Java model and problems in the C++ model. --yigalfor each different comp function there will be a different sort instance created by the compiler.But surely, every container must itself be templatised on the element? I mean, unless you go the Java way and /only/ have a container for Objects. So your plan would instantiate at least one sort function for every element type. Moreover, since your OrderedCollection interface would demand that every container must implement sort, then every different kind of collection would have it's own sort function. So under your scheme, the total number of sort functions would equal the total number of container types. In other word, if your program used List!(char) list1, list2; List!(int) list3; List!(C) list4, list5; Array!(double) array1; then there would be four sort functions. In what way is that fewer than would be produced by sort(list1); sort(list2); sort(list3); sort(list4); sort(list5); sort(array1); ? The way I see it, we both get four. However, if choose /not/ to sort list3, then I only get three instantiations, but you still get four.What i'm trying to say is that using any one tool for everything is wrong.You should have just said that to start with, because it's a statement of the obvious and no one will disagree with you. :-)I'm not arguing that delegates should replace all template code in D! I'm arguing that both solutions need to be evaluated and the better one chosenWhy are you assuming that that didn't happen?also note that these two options overlap sometimes and (when it's suitable) both solutions should be provided so the user could choose his preferred trade-off (time vs. memory).Except that, if you can trivially create one from the other, then providing one is effectively providing both anyway.
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:From what I know, an efficient STL implementation would use C style containers with void* under the hood to minimize executable bloat.I /think/ I get what you're saying here, which is (correct me if I'm wrong, but) given class C {} class D {} then Vector!(C) c; Vector!(D) d; produces two instantiations of Vector! - one for C and one for D - and yet those instantiations will produce byte-for-byte identical machine code. However - this is a linker problem, not a template problem, and Walter has said that this will be fixed in the future. So in the future, whenever any two or more segments consist of identical bytes, the linker may keep one and throw the rest away. The moral of the story is: specify your intent, but leave the implementation to the compiler/linker. If you try to "outguess" the compiler, by instantiating only one single Vector!(void*), then in the long run, all you are doing is creating obfuscated code for short term gain.
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I don't know about any plans to change the dm linker. it is currently written directly in assembly and highly optimized. any change would require a lot of work and probably a rewrite. also, if walter ever decides to work on the linker he should change the format of the object files. Personally i don't understand a great deal about that subject, but i got the impression in this NG from more knowledgeable people that such a change is needed. what i mean was doing something like this: for example for list - defining a regular LinkedListImpl class which holds void* as items, and then defining a class LinkedList(T) which uses internally the LinkedListImpl class. this way the templates and interface hierarchy is separate from the implementation. the templatized interfaces and classes in the hierarchy provide the high level API and the *Impl classes provide efficient implementations without bloating the executable code. you get a different instances of vector as per your example for each unique type T, BUT those instantiations are merely small shells that call common code in the matching vectorImpl class. that is not the same as using linkedList!(void*) since you do get a layer that provides the convenience and type safety. If the linker/compiler could automate this, that would be great. but it can be accomplished without that too.From what I know, an efficient STL implementation would use C style containers with void* under the hood to minimize executable bloat.I /think/ I get what you're saying here, which is (correct me if I'm wrong, but) given class C {} class D {} then Vector!(C) c; Vector!(D) d; produces two instantiations of Vector! - one for C and one for D - and yet those instantiations will produce byte-for-byte identical machine code. However - this is a linker problem, not a template problem, and Walter has said that this will be fixed in the future. So in the future, whenever any two or more segments consist of identical bytes, the linker may keep one and throw the rest away. The moral of the story is: specify your intent, but leave the implementation to the compiler/linker. If you try to "outguess" the compiler, by instantiating only one single Vector!(void*), then in the long run, all you are doing is creating obfuscated code for short term gain.
May 11 2008
On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I don't know about any plans to change the dm linker.You do now. :-) The following is an exact word-for-word quote, posted 23 Mar by Walter Bright: "Templatizing is the language solution. I know about the bloat problem, but that is really an implementation issue. I know the linker doesn't do it now, but identical blocks of generated code with different names should be merged."what i mean was doing something like this: <snip>And that's a perfectly reasonable strategy, but it only works for pointers to structs. (In D, it won't work for class references). Structs, by contrast, should probably be written into a collection by value, not by reference. In short, it's a C++-specific strategy.
May 11 2008
Janice Caron wrote:On 12/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I know about that quote. that doesn't mean anything. saying that something should be done in a specific way is not the same as saying one would work to make that something work in that specific way. Walter acknowledges some problems with the linker, he also said that fixing that is a huge amount of work for the reasons I've stated in a previous post. so that doesn't mean Walter is planning to fix those problems in the near future.I don't know about any plans to change the dm linker.You do now. :-) The following is an exact word-for-word quote, posted 23 Mar by Walter Bright: "Templatizing is the language solution. I know about the bloat problem, but that is really an implementation issue. I know the linker doesn't do it now, but identical blocks of generated code with different names should be merged."hmm... you're right... :( maybe there is a way to adapt that way of thinking to D or maybe this would be optimized automatically by the liker in the future, as you say all i can say is that I hope the linker will be worked on...what i mean was doing something like this: <snip>And that's a perfectly reasonable strategy, but it only works for pointers to structs. (In D, it won't work for class references). Structs, by contrast, should probably be written into a collection by value, not by reference. In short, it's a C++-specific strategy.
May 11 2008
Yigal Chripun wrote:hmm... you're right... :( maybe there is a way to adapt that way of thinking to D or maybe this would be optimized automatically by the liker in the future, as you say all i can say is that I hope the linker will be worked on...(light-bulb) since D has Object from which all the classes inherit (unlike C++) the same idea I previously suggested could be employed, only instead of void* use Object. again, since it is buried inside the implementation, you still get proper generic interfaces and classes. this sounds even cleaner than void*. what do you think?
May 11 2008
Yigal Chripun Wrote:Dee Girl wrote:I do not think so. In fact I am sure you are entirely wrong this time. mysort has a signature that is not parameterized. The language definitions (I just read again) implies the model of compiling is that not-parameterized functions have only one implementation. If mysort has only one implementation, it is not possible for mysort to use different instantiations of sort. It is called the pigeonhole principle. It uses only one instantiations for all calls. Unless D generates code at runtime. ^_^Yigal Chripun Wrote: This should be good for the standard library. It is the most flexible approach. You can always do what you want in one line: void mysort(int array[], bool delegate comp(int, int)) { sort!(comp)(array); } Now you can call mysort many times and there is only one version of std.sort (if I understand it correctly).not exactly. there's always a balance between memory and time, if you improve the first second worsens. for each different comp function there will be a different sort instance created by the compiler.a simple example: struct num(N) { int number = N; } num!(1) is different from num!(2); so with this method you'll get a bigger executable that contains the sort code twice.I understand that very well (better than English...) I did just implemented a compiler for a mini-pl0 language with generics in class. These problems of code duplication are very clear when I wrote the compiler my self! ^_^ But maybe you did not understand how mysort works without duplicating code.with my way on the other hand you'll always have only one sort function. the cost in my case is that sort will always call the comp function to compare while the template can avoid that function call. simply time vs. memory. memory is cheap nowadays and the linker could be made better to try to minimize the memory footprint. on the other hand cpu chips today come with multi-cores and the compiler could optimize many calls to small functions. so in the obj file, the compiler can inline the function body instead of calling it. the main question would be: what are you optimizing for? if you develop on a cellphone memory could be more important then time, but on a missile a nano second could mean the missile missed its target. so it all depends on what you want to achieve.If you believe this then you should be happy with std.sort. Which gives the option to optimize for what you want. All other sort implementation I read only go one way and it's hard to make them go an other way.But you want to use one tool for all job. You want indirect calls and they force your design only one way.But think what if your mysort is the only implementation given in the standard library. Maybe sometimes it is too slow for your program. What do you do? void fastsort!(alias comp)(int[] array) { bool dg(int a, int b) { return comp(a, b); } mysort(array, dg); } This will not be faster at all ^_^. As a friend said: You can build a dynamic library from a static one but not inverse. Things only stack one direction. I think it is good the way sort is now in the standard library, it offers the maximum possible and opens all options. It is very impressive for someone coming from many other language.My rant emphasizes specific things, since that's the point of a rant. I could talk about all the things I like in C++ and Janice would agree and we won't have anything interesting to talk about. I do use c++ and it is a powerful language. My point is not that I'm against templates, or that I hate C++. What i'm trying to say is that using any one tool for everything is wrong.Now, when you use a string with the template, the sort will contain the code itself inline to the sort instance so it "saves" you a function call. IMO, the string solution is not clean ( C MACROs? )and while for simple cases it could save you a function call it's premature optimization IMO.To me it looks very convinent!Also, if I use delegates, I can't see how a template can save that function call. so it just bloats my executable with unneeded copies of sort.You can use mysort above. It is my first contribution to D. ^_^I hate when library code forces me to this. I think the programmer knows his code better than the library writer and should be able to choose himself whether he needs to use a compile time solution to save function calls or a run-time solution to prevent unnecessary executable bloat. this optimization should be made by the programmer and _not_ the library writer. This solution does not give me that freedom.It looks like you understand the exact oposite. std.sort gives you all freedom, anything else would give less. I have studied many languages but none offers the same flexibility. Functional languages comparisons. C++ force you to go outside your function and write the comparison even simple as a.member < b.member! D is the best and std.algorithm very elegant.this solution is a classic STL piece of code, BUT, the main difference is that for C++ this is probably the only reasonable solution as C++ doesn't have delegates. this is why when coding for D I urge people to rethink their solution instead of just doing what is best in C++ (or any other preferred language of the library writer). This is a prime example why the C++ solution is wrong for D.The other rant you wrote and the interesting discussion with Janice are good to read. But there is little objective points you bring. I see you do not like anything like C++ and then you go back and try to make arguments. One objective point is you like a container hierarchy. I have worked with them and they can be useful. But like with mysort the same argument works. You can build a hierarchy of containers on top of fast containers. But not the inverse. Dee GirlI'm not arguing that delegates should replace all template code in D! I'm arguing that both solutions need to be evaluated and the better one chosen, also note that these two options overlap sometimes and (when it's suitable) both solutions should be provided so the user could choose his preferred trade-off (time vs. memory).Maybe once it is clear the inclusion relationship: that with the template you can have dynamic. Then you will be happy. Dee Girl
May 11 2008
your reply is correct but it is misleading. a different example: void Fn() { int i; } every time the function is called a new int i is generated on the stack and destroyed when the function is exited. so for a specific call to Fn there can be only one variable i, in the course of the program that same variable is created and destroyed many times ( for each call to the function). you are correct in saying that there is only one mysort function but every time you call it with a different comp delegate it needs to have a different sort instance. the difference is that the int i is generated on the stack but all those different sort instances are not. those are code which goes into the executable. so while in any specific time mysort uses only one sort instance, over the course of the entire program the sort template can get instantiated more than once. does it make more sense to you, now? --Yigal
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:your reply is correct but it is misleading.Whose reply? What is this a reply to? You might want to continue replying point-by-point. It makes things a lot easier to follow.you are correct in saying that there is only one mysort functionWho said that, and in what context?but every time you call it with a different comp delegate it needs to have a different sort instance.OK, I see what you're saying. If I do sort!(English)(text); sort!(Czech)(text); then I do indeed get two instantiations (which presumably will sort text alphabetically by English and Czech rules respectively). HOWEVER. If I write void mysort(T)(T[] array, int delegate(T,T) compare) { sort!(compare)(array); } then I have implemented your desired solution, trivially. And now I can call mysort(array,English); mysort(array,Check); and still there's only one instantiation of sort. It's exactly what Dee's been saying all along. You can make dynamic from static, but you can't make static from dynamic.
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:sorry for that, that is a reply to dee.your reply is correct but it is misleading.Whose reply? What is this a reply to? You might want to continue replying point-by-point. It makes things a lot easier to follow.you are correct in saying that there is only one mysort functionWho said that, and in what context?I don't follow. How can there be only one sort instance? the mysort function uses both sorts so the code for both needs to be in the executable...the first call instantiates the first sort for English, and the second instantiates the second sort for Czech.but every time you call it with a different comp delegate it needs to have a different sort instance.OK, I see what you're saying. If I do sort!(English)(text); sort!(Czech)(text); then I do indeed get two instantiations (which presumably will sort text alphabetically by English and Czech rules respectively). HOWEVER. If I write void mysort(T)(T[] array, int delegate(T,T) compare) { sort!(compare)(array); } then I have implemented your desired solution, trivially. And now I can call mysort(array,English); mysort(array,Check); and still there's only one instantiation of sort. It's exactly what Dee's been saying all along. You can make dynamic from static, but you can't make static from dynamic.
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I don't follow. How can there be only one sort instance?Because I only instantiated it once - specifically with the delegate named "compare".the mysort function uses both sortsNo, the mysort function is handed a delegate an input parameter.so the code for both needs to be in the executable...The comparison functions need to be in the executable, but not two separate instantiations of sort. Why is my last post not clear? You get two instantiations with sort!(English)(text); sort!(Czech)(text); because I statically passed in two separate delegates, but you only get one instantiation with sort!(compare)(array) because I statically passed in only one delegate. That you passed two into mysort is irrelevant, because that's a runtime thing.
May 11 2008
Janice Caron Wrote:Why is my last post not clear? You get two instantiations with sort!(English)(text); sort!(Czech)(text); because I statically passed in two separate delegates, but you only get one instantiation with sort!(compare)(array) because I statically passed in only one delegate. That you passed two into mysort is irrelevant, because that's a runtime thing.Thank you. This is a clear explaining. It took me long time to write my massage with disassembling a program! I wish I saw your post first. I would not have to write it ^_^ Thank you, Dee Girl
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:OK, I think i understand you now. there is only one sort instance that calls compare. compare changes at run-time. in that case, I was wrong. sorry for the confusion.I don't follow. How can there be only one sort instance?Because I only instantiated it once - specifically with the delegate named "compare".the mysort function uses both sortsNo, the mysort function is handed a delegate an input parameter.so the code for both needs to be in the executable...The comparison functions need to be in the executable, but not two separate instantiations of sort. Why is my last post not clear? You get two instantiations with sort!(English)(text); sort!(Czech)(text); because I statically passed in two separate delegates, but you only get one instantiation with sort!(compare)(array) because I statically passed in only one delegate. That you passed two into mysort is irrelevant, because that's a runtime thing.
May 11 2008
Yigal Chripun Wrote:your reply is correct but it is misleading. a different example: void Fn() { int i; } every time the function is called a new int i is generated on the stack and destroyed when the function is exited. so for a specific call to Fn there can be only one variable i, in the course of the program that same variable is created and destroyed many times ( for each call to the function). you are correct in saying that there is only one mysort function but every time you call it with a different comp delegate it needs to have a different sort instance. the difference is that the int i is generated on the stack but all those different sort instances are not. those are code which goes into the executable. so while in any specific time mysort uses only one sort instance, over the course of the entire program the sort template can get instantiated more than once. does it make more sense to you, now?I am sorry Yigal. What you say makes no sense. Your previous reply is wrong and this reply is even more wrong. It is simple logic. mysort has only one body. Inside that body is has a regular call to a function that implements the sorting. That call can only go to one function! It is very clear. D does not generate code at runtime. It is a silly exercise but we can do this. Please compile this program. === test.d import std.algorithm; import std.stdio; void mysort(int[] array, bool delegate(int, int) comp) { sort!(comp)(array); } void main() { int[] array = [ 1, 2, 3, 4 ]; bool comp1(int a, int b) { return a > b; } mysort(array, &comp1); //writeln(array); bool comp2(int a, int b) { return a < b; } mysort(array, &comp2); //sort!(comp2)(array); //writeln(array); } === writeln is taken out so it does not get confusing. Please run dmd -g test.d objdump -d test.o|less Search for word "section" in the disassembled file. There are 19. It seems each function has its section. 1. section .text is the all file 2. section .text._D4test6mysortFAiDFiiZbZv is the mysort function. I do not know the naming scheme of D but it is easy to guess which function is. 3. section .text._Dmain is the main funciton 4. section .text._D4test4mainFZv8compare1MFiiZb is compare1 5. section .text._D4test4mainFZv8compare2MFiiZb is compare2 6. section .text._D4test6mysortFAiDFiiZbZv108__T4sortS36_D4test6mysortFAiDFiiZbZv4compDFiiZbVE3std9algorithm12SwapStrategy0S233std9algorithm8ite SwapTAiZ4sortMFAiZv is the std.sort function 7. section .text._D3std8iterator12__T5beginTiZ5beginFAiZPi is begin funciton 8. section .text._D3std8iterator10__T3endTiZ3endFAiZPi is end function 9. section .text._D4test6mysortFAiDFiiZbZv112__T8sortImplS36_D4test6mysortFAiDFiiZbZv4compDFiiZbVE3std9algorithm12SwapStrategy0S233std9algorithm8iterSwa TAiZ8sortImplMFAiZv is the sortimpl function 10. section .text._D4test6mysortFAiDFiiZbZv112__T8sortImplS36_D4test6mysortFAiDFiiZbZv4compDFiiZbVE3std9algorithm12SwapStrategy0S233std9algorithm8iterSwapTAiZ8sort mplMFAiZv4predMFiZb is the pred function 11. section .text._D4test6mysortFAiDFiiZbZv55__T8getPivotS36_D4test6mysortFAiDFiiZbZv4compDFiiZbTP Z8getPivotMFPiPiZPi is the getPivot function 12. section .text._D3std9algorithm16__T8iterSwapTPiZ8iterSwapFPiPiZv is the iterSwap function 13. section .text._D3std9contracts17__T8pointsToTiTiZ8pointsToFKiKiZb is the pointsTo function 14. section .text._D3std9algorithm11__T4swapTiZ4swapFKiKiZv is the swap function 15. section .text._D3std8iterator12__T5rangeTiZ5rangeFPiPiZAi is the range function 16. section .text._D4test6mysortFAiDFiiZbZv112__T8sortImplS36_D4test6mysortFAiDFiiZbZv4compDFiiZbVE3std9algorithm12SwapStrategy0S233std9algorithm8iterSwapTAiZ8sortImplMFAiZv243__T9partitionS165_D4test6mysortFAiDFiiZbZv112__T8sortImplS36_D4test6mysortFAiDFiiZbZv4compDFiiZbVE3std9algorithm12SwapStrategy0S233std9algorithm8iterSwapTAiZ8sortImplMFAiZv4predMFiZbVE3std9algorithm12SwapStrategy0S233std9algorithm8iterSwapT iZ9partitionMFAiZPi is the partition function 17. section .text._D4test6mysortFAiDFiiZbZv55__T8isSortedS36_D4test6mysortFAiDFiiZbZv4compDFiiZ TAiZ8isSortedMFAiZb is the isSorted function 18. section .text._D4test6mysortFAiDFiiZbZv55__T8isSortedS36_D4test6mysortFAiDFiiZbZv4compDFiiZbTAiZ8isSor edMFAiZb4predMFiiZb is another isSorted function (I do not now why there are two) 19. section .text._D4test6mysortFAiDFiiZbZv55__T8isSortedS36_D4test6mysortFAiDFiiZbZv4compDFiiZbTAiZ8isSortedMFAiZb133__T12findAdjacentS108_D4test6mysortFAiDFiiZbZv55__T8isSortedS36_D4test6mysortFAiDFiiZbZv4compDFiiZbTAiZ8isSortedMFAiZb4predMFiiZbTAiZ1 findAdjacentMFAiZPi is the isAdjacent function. It is clear now that there is only one instantiation of std.sort. I hope now it is all clear. Thank you, Dee Girl
May 11 2008
Dee Girl wrote: <snip> thanks for the effort explaining me the issue. I've already understood my mistake thanks to Janice' explanation, though.
May 11 2008
Disclaimer: this post reflects my personal opinion only and should not start yet another flame war about tango vs phobos. feel free to disagree with everything I say. based on what I know: phobos uses the C IO. this is both bad for performance (yet another layer) and the interface is ugly. Tango's IO much more powerful, more efficient, and more flexible. the cost of it is that it's more complex (compared to the C-like functions phobos provides). phobos is ill organized mixing the run-time library (which you as a user should never touch) with the user code. Why is the GC internals in the same library as the string manipulation functions?? also the namespace in tango is much better organized into packages and modules (although, sometimes it's too much, like the collections being nested in utils). I prefer Tango's tango.io.Stdout vs std.stdio. I think Stdout is lame but at least it's inside an IO package. what's the deal with using 8-letter acronyms that are hard to remember anyway? we are not limited by DOS' 8 chars length for file names anymore. Tango has much more functionality, is optimized for performance, has less bugs and more maintainers than phobos. Walter is a genius compiler writer but he can't spend all his time on phobos, And I really dislike the C++ smell from all of the code Andrei added to phobos. He's a C++ Wizard, But that C++ mindset that templates solve everything is bad IMO. sometimes, the language does need to provide proper syntax. D needs a good tuple support and not a template Tuple!(...) for example. another example would be all the templates in std.algorithm. I'd prefer a reduce function that takes a delegate rather than a reduce template. Regarding C++ and STL: it is useful for C++, however it is designed in a way to take into account all the deficiencies of C++ (which D doesn't have). an error in your c++ code that uses STL will produce a screen full of text which is unrelated to the problem, making it useless. STL does not use polymorphism in any way so that if i used an iterator and decided to reverse the iteration I need to make sure I change the Iterator type, which is lame (had this bug, once). this complete lack of polymorphism is plain wrong. simple example: I use a vector to go over series of values and then decide to use a linked-list instead. I need to go and change my code everywhere, since there are no common interfaces like Java's List or collection for that matter. in java i can have: List<C> list = new ArrayList<C> (); changed to: List<C> list = new LinkedList<C> (); and that's it! no need to search and replace all my functions (since they use the general List<C> anyway. Isn't that so much more productive? C++ needs to add language extensions (i.e. concepts) to make that work. My point is that I do not want that design of the STL. also, C++ uses iterators extensively, and this design can be replaced by more advanced concepts like generators and HOF. I prefer smalltalk and ruby way of collection.each { code } vs. C++ more primitive: for (collection::iterator i = collection.begin(); i != collection.end() ; ++i) { code } which one of those is better? smalltalk has map:, select: inject:into: etc, built into the collections classes, which is SO much better than using C++ iterators. STL does provide similar constructs but they are so verbose that no one ever uses them. D needs to become a compiled efficient ruby-like language more than a slightly more readable C++. Those are my rumblings, If somebody got offended, than I'm truly sorry for that. I just wish for a better D future. --Yigal
May 10 2008
Yigal Chripun wrote:D needs to become a compiled efficient ruby-like language more than a slightly more readable C++.D'accord. --
May 10 2008
Yigal Chripun wrote:IMHO, your reply makes perfect sense for C/C++ but not for D. specifically because D has other facilities to handle those cases. a dchar (or [w]char) _must_ always contain valid data. if you need to store other encodings you can use ubyte instead which does not limit you to a specific bit pattern (this is why D has it in the first place...) the above example of slices can be easily dealt with since (unlike in C/C++) D arrays know their length. this is similar to the fact that D checks bounds on arrays and throws on error (in debug mode) whereas C/C++ does not. IMO, the D implementation itself (both the compiler and the runtime) need to make sure chars are always valid. this should not be something optional added via a library. I agree with you notion of levels, I just think D provides for much better facilities for low-level coding compared to using unsafe C/C++ conventions. int n; dchar c = cast(dchar)n; dchar d = cast!(dchar)n; in the above code, the second one should be used and it might throw. the first simply does not make any sense and should produce a compiler error because you cannot convert an int value to a dchar (unless it's a one digit int) <off topic rant> What worries me most about D is the fact that D becomes an extension to C++. The whole idea behind D was to create a new language without all the baggage and backward compatibility issues of C++. I don't want a slightly more readable version of C++ since I'll get that with C++0x. c++ programmers want D to have a D "stl" and a D boost. that's wrong! STL is badly designed and employs massive amounts of black magic that ordinary people do not understand. (I suffer at work while writing in C++). in what world does it make sense to mark an abstract method with "=0;" at the end, especially when the method is horizontally long and that gets to be off screen! D should be written with a D mindset which should be the best ingredients extracted from all those languages D got its influences designing such a new D mindset, IMO. Phobos is not, since it's merely C code written with D syntax, with all those new shiny code Andrei added which is C++ code written with D syntax. I appreciate his great expertise in C++, but I already can use C++ libraries in C++ without learning a new language. D needs to be better. *much* better. </rant> --YigalI think I misread your example so I want to clarify: chars should contain only valid utf code points and not any other bit-pattern. since code-points need to be ordered in specific ways it make sense that the D standard library would provide methods that validate and/or fix utf strings. However, any other encoding must use ubyte arrays instead. What if: int num = ...; dchar ch = cast(dchar)num; dchar ch1 = cast!(dchar)num; ch would contain the bit pattern of the num-th code-point in the utf standard (throwing for numbers not in the utf encoding table) and the second cast would operate on the bit level (like an reinterpret_cast) and throw if the resulting dchar bit pattern is not valid. also, I'm leaning towards reporting all cast run time errors with exceptions (it's more consistent, since you cannot return null for primitives). no need for that special return null case. (if D had attributes, i would have suggested making a suppress-exceptions attribute for that purpose) --Yigal
May 10 2008
On 10/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:a dchar (or [w]char) _must_ always contain valid data.That has never been the case, ever. If you think that D should change, so as always to enforce Permanently Valid Unicode, then start a new thread and make a proposal. We'll discuss.
May 10 2008
BCS Wrote:The major issue I have with this is that the construct that actually does the downcast MUST also do the type check. Therefor it gets done twice and this is a bit of a performance issue.current implementation of cast already does the typecheck, so there will be no performance issue.
May 10 2008
Reply to terranium,BCS Wrote:but the proposal would requier *two* checks in the "use this as a B if it's a B" case. One for the code to "if" on and one in the actual conversion (to be safe).The major issue I have with this is that the construct that actually does the downcast MUST also do the type check. Therefor it gets done twice and this is a bit of a performance issue.current implementation of cast already does the typecheck, so there will be no performance issue.
May 10 2008
I'm sorry if this post sounds argumentative, but I disagree strongly with pretty much everything in this post. So no hard feelings, peace, respect, all that ;-P. Janice Caron wrote:Better, safer casts have been requested before - in fact I routinely see that request on the monthly "D wishlist" that goes round.I doubt any of the suggestions include "at the expense of the current cast". If all these were implemented, it would be a significant cognitive load on the programmer, and D is already a fairly complex language. Also, should getting rid of keywords really be a design goal? Only Walter (and you, apparently; no disrespect) seem to think so.But here's a fairly concrete suggestion as to what the syntax might be. And as a bonus, we'd get rid of the "cast" keyword, thus reducing the keyword count by one. So, here are the various types of transformation I suggest: (*) Build a new instance with a different memory layout, but with the same value. (For example, casting double to int). For lossless conversions, this can happen implicitly, but for lossy conversions, an explicit cast is necessary. For D, I suggest to!(T)(x) where to! is a template function provided by std.conv (not a keyword). to! already performs many conversions. Throwing in the basics like to!(int)(double) would be an obvious extension.Ick! That means another identifier that needs to be a compiler built-in thus increasing coupling between the compiler and the standard library. Since it's not something the template can actually handle, why should it be in there rather a special construct in the core language?(*) Use RTTI to cast up and down the class heirarchy. In C++, this would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the cast: class!(T)(x) to do that actual upcast or downcast - but RTTI dynamic casts can fail, so the question arises: What should we do if the cast fails? Should we return null, or should we throw an exception. My view is that we should throw an exception, but also introduce a new construct to test whether or not the cast would be possible in the first place: is!(T)(x) which returns bool. is!(T) would be like Java's instanceof. Thus, one could write void f(A a) { if (is!(B)a) { B b = class!(B)(a); /*...*/ } else if (is!(C)a) { C c = class!(C)(a); /*...*/ } } etc.But all this is possible with the current cast system. All you gain is a more complex syntax, and since "cast" is a term well-known to programmers, you lose a meaningful bit of syntax.(*) Reinterpret a bit pattern. In C++, this would be reinterpret_cast<T>(x). Without changing the memory layout, or the constancy, reinterpret the bits to mean something else. For D, I suggest union!(T)(x)Ditto as above. Just means the person needs to think about something else. And why "union", that seems like an arbitrary keyword. WHy not "foreach_reverse!(T)(x)" which makes just as much sense.(*) Change const to mutable, or invariant to mutable. In C++, this would be const_cast<T>(x). There is no equivalent in D, however, in D, one can currently write cast(T)x, and constancy will be magically (and dangerously) waved away. In the new scheme, I suggest: auto!(T)(x)How is that better than "cast(T) x;". I guess just because it creates the distinction?(*) Change const to invariant, or mutable to invariant. Currently, the syntax for this is cast(invariant)x, however this has two disadvantages: (i) the entire type is made invariant, and you might want to invariantize only part of the type, and (ii) cast(T)x also works. In other words, (i) is not powerful enough, and (ii) is too dangerous. There is no equivalent for this in C++, since C++ has no invariance. For D, I suggest: invariant!(T)(x)Again, how is that any better? --- Okay, sorry that was so negative. But it just seems that cast() works perfectly fine for _all_ those things right now. If what you want is safer casting, you can write your own templates to do that.
May 09 2008
Robert Fraser wrote:[...]Also, I find the cast-returns-null-on-failure behavior quite useful. In your system, this piece of code: Shape s = getShape(); if(auto r = cast(Rectangle) s) { return r.length * r.width; } else if(auto c = cast(Circle) s) { return PI * c.radius * c.radius; } ... would become ... Shape s = getShape(); if(is!(Rectangle)(s)) { auto r = class!(Rectangle) s; return r.length * r.width; } else if(is!(Rectangle)(s)) { auto c = class!(Circle) s; return PI * c.radius * c.radius; } Which is more typing and uglier IMO. Since if you want this behavior, you can already make templates to do it, why force it down people's throats?
May 09 2008
Robert Fraser Wrote:Robert Fraser wrote:... should become ... :)))) Shape s = getShape(); return s.square();[...]Also, I find the cast-returns-null-on-failure behavior quite useful. In your system, this piece of code: Shape s = getShape(); if(auto r = cast(Rectangle) s) { return r.length * r.width; } else if(auto c = cast(Circle) s) { return PI * c.radius * c.radius; } ... would become ... Shape s = getShape(); if(is!(Rectangle)(s)) { auto r = class!(Rectangle) s; return r.length * r.width; } else if(is!(Rectangle)(s)) { auto c = class!(Circle) s; return PI * c.radius * c.radius; }
May 10 2008
On 09/05/2008, Robert Fraser <fraserofthenight gmail.com> wrote:I'm sorry if this post sounds argumentative, but I disagree strongly with pretty much everything in this post. So no hard feelings, peace, respect, all that ;-P.All is peaceful here on the newsgroup. This is just a nice, friendly chat. Nothing has been suggested "officially", and nothing will be, if folk here don't like the idea.Um. No. std.conv.to!(T) already exists. I guess what you're saying is, it's not possible to write a function like toInt(double) if the cast operator is gone. I hadn't thought of that. In that case, maybe it should be cast!(T) // lossy conversion to!(T) // conversion that always succeeds, or throws an exceptionto!(T)(x) where to! is a template function provided by std.conv (not a keyword). to! already performs many conversions. Throwing in the basics like to!(int)(double) would be an obvious extension.Ick! That means another identifier that needs to be a compiler built-inAnd why "union", that seems like an arbitrary keyword.It wasn't entirely arbitrary. A union /is/ a reinterpretation of bits. It's one memory layout painted over another. (And it's a lot shorter than "reinterpret_cast").I guess I just couldn't think of anything better. :-) D doesn't have a word for "mutable", and C++'s "const_cast" is too long. (And also misleading, since its primary use is to /remove/ constancy, not add it).auto!(T)(x)How is that better than "cast(T) x;". I guess just because it creates the distinction?Okay, sorry that was so negative. But it just seems that cast() works perfectly fine for _all_ those things right now. If what you want is safer casting, you can write your own templates to do that.You can? I confess, my template writing skills aren't up to the job. If it could be done, that would be interesting. Obviously, you'd have to use non-reserved words for the names though. I'm curious to know if C++'s dynamic_cast<T>, const_cast<T> are implemented with templates. They certainly /look/ like C++ templates.
May 09 2008
On Fri, 09 May 2008 19:58:49 +0100, Janice Caron <caron800 googlemail.co= m> = wrote:On 09/05/2008, Robert Fraser <fraserofthenight gmail.com> wrote:=I'm sorry if this post sounds argumentative, but I disagree strongly =with pretty much everything in this post. So no hard feelings, peace, =respect, all that ;-P.All is peaceful here on the newsgroup. This is just a nice, friendly chat. Nothing has been suggested "officially", and nothing will be, if=folk here don't like the idea.d).to!(T)(x) where to! is a template function provided by std.conv (not a keywor=-into! already performs many conversions. Throwing in the basics like to!(int)(double) would be an obvious extension.Ick! That means another identifier that needs to be a compiler built=Um. No. std.conv.to!(T) already exists. I guess what you're saying is, it's not possible to write a function like toInt(double) if the cast operator is gone. I hadn't thought of that. In that case, maybe it should be cast!(T) // lossy conversion to!(T) // conversion that always succeeds, or throws an exceptionAnd why "union", that seems like an arbitrary keyword.It wasn't entirely arbitrary. A union /is/ a reinterpretation of bits.=It's one memory layout painted over another. (And it's a lot shorter than "reinterpret_cast").s =auto!(T)(x)How is that better than "cast(T) x;". I guess just because it create=the distinction?I guess I just couldn't think of anything better. :-) D doesn't have a=word for "mutable", and C++'s "const_cast" is too long. (And also misleading, since its primary use is to /remove/ constancy, not add it).sOkay, sorry that was so negative. But it just seems that cast() work==perfectly fine for _all_ those things right now. If what you want is =They look like templates deliberately because they do a similar job but are not (in any implementation I have seen). The same could be made true for any "cast!" magic. I think this is one place where breaking the rules is more acceptable. That said, I think the reason we still hav= e = C style casts in C++ is because they are the only way to implement cast template= s. = Probably in the mysts of time this was how it was done. Does anyone have a copy o= f "the design and evolution of C++" to hand? I bet the answer is in there.= On another note I like that reinterpret_cast and const_cast are hard to = = type. It slows you down and hopefully encourages you to think about why you mi= ght be doing something evil. Another poster's comment that reinterpret_casts are mostly to and from = void* hit home for me. I don't think this is as much of a problem when your hierarchy i= s = based on object though. I also don't like your choice of union. I can see the C = connection but it doesn't say re-interpret to me. Something more like good=3Dforce!(evil) = = appeals. When reviewing and correcting C++ code I make use of the _cast anchor as= = something (a code smell) to search for. This would be harder for single words. My = = language design axiom here would be longer names for more dangerous uses. Otherwise I like the proposal. I hope the discussion leads to something = = more concrete. Regards, Bruce.safer casting, you can write your own templates to do that.You can? I confess, my template writing skills aren't up to the job. If it could be done, that would be interesting. Obviously, you'd have to use non-reserved words for the names though. I'm curious to know if C++'s dynamic_cast<T>, const_cast<T> are implemented with templates. They certainly /look/ like C++ templates.
May 09 2008
Robert Fraser Wrote:Also, should getting rid of keywords really be a design goal? Only Walter (and you, apparently; no disrespect) seem to think so.I agree here. I think that language should be simple and understandable, I see no difference between new construct and new keyword, if new construct is easy to understand, then it's good, but if it's not, no mysterious goal of minimizing number of keywords should be pursued at the cost of strange constructs.
May 10 2008
Yigal Chripun Wrote:D should be written with a D mindset which should be the best ingredients extracted from all those languages D got its influences D needs to be better. *much* better.This is a really hard task. Proper design is probably the hardest thing in programming.
May 10 2008
Yigal Chripun Wrote:I prefer smalltalk and ruby way of collection.each { code }D foreach is very advanced and expressive in this. Though not in ruby way, but
May 10 2008
Yigal Chripun Wrote:Disclaimer: this post reflects my personal opinion only and should not start yet another flame war about tango vs phobos. feel free to disagree with everything I say. based on what I know: phobos uses the C IO. this is both bad for performance (yet another layer) and the interface is ugly. Tango's IO much more powerful, more efficient, and more flexible. the cost of it is that it's more complex (compared to the C-like functions phobos provides). phobos is ill organized mixing the run-time library (which you as a user should never touch) with the user code. Why is the GC internals in the same library as the string manipulation functions?? also the namespace in tango is much better organized into packages and modules (although, sometimes it's too much, like the collections being nested in utils). I prefer Tango's tango.io.Stdout vs std.stdio. I think Stdout is lame but at least it's inside an IO package. what's the deal with using 8-letter acronyms that are hard to remember anyway? we are not limited by DOS' 8 chars length for file names anymore. Tango has much more functionality, is optimized for performance, has less bugs and more maintainers than phobos. Walter is a genius compiler writer but he can't spend all his time on phobos, And I really dislike the C++ smell from all of the code Andrei added to phobos. He's a C++ Wizard, But that C++ mindset that templates solve everything is bad IMO. sometimes, the language does need to provide proper syntax. D needs a good tuple support and not a template Tuple!(...) for example. another example would be all the templates in std.algorithm. I'd prefer a reduce function that takes a delegate rather than a reduce template.I am not sure I understood all how D templates work. But how it seems is this. Please correct if it is wrong. If reduce takes a template argument then expands a loop each time in client code. If reduce as you want takes a delegate argument then there is only one loop but there is one indirect call for each iteration. Question is which one is better. Conceptually they have same power. There are details of performance. I think first one is best because really offers you both. The second only limits your choice. But they have same power, why would you hate one and love another? Thank you, Dee Girl
May 11 2008
Dee Girl wrote:I am not sure I understood all how D templates work. But how it seems is this. Please correct if it is wrong. If reduce takes a template argument then expands a loop each time in client code. If reduce as you want takes a delegate argument then there is only one loop but there is one indirect call for each iteration. Question is which one is better. Conceptually they have same power. There are details of performance. I think first one is best because really offers you both. The second only limits your choice. But they have same power, why would you hate one and love another? Thank you, Dee Girlread my other reply about time vs. space. I prefer a delegate since to me it looks cleaner. I don't know how Andrei implemented it, but either the template inlines stuff and the benefit would be less time more space (again, the balance can change due to linker/compiler) if not than it would be the same as the delegate version. only it'll bloat the executable since for each unique delegate there will be a copy of the template. If my understanding here is wrong, feel free to correct me. [Janice?] --Yigal
May 11 2008
On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:I don't know how Andrei implemented it, but either the template inlines stuffIf the function is called N times (where N > 0), the template is instantiated exactly once. The function /may/ get inlined, but this will be true of all functions, not just template functions. In D, there is no way to say "never inline this function".if not than it would be the same as the delegate version.Yes.only it'll bloat the executable since for each unique delegate there will be a copy of the template.It is certainly possible to ensure that there is exactly one delegate type for each element type, so the number of copies can be made minimal.If my understanding here is wrong, feel free to correct me. [Janice?]See separate post. By my reckoning, your OrderedCollection interface results in more executable bloat than use of std.algorithm.sort. (But if my understanding of your idea is wrong, feel free to correct me).
May 11 2008
Janice Caron wrote:On 11/05/2008, Yigal Chripun <yigal100 gmail.com> wrote:if what you say is true ( and i don't have any reason to believe otherwise) than the use of templates here is redundant. I like the idea of open classes very much (and wait for it to get implemented for D2) since this removes the inconsistency between methods and free functions. the idea is that the classes would contain the minimal needed set of methods (those there need polymorphism) and other functionality can be provided with free functions, _but_ with a consistent interface. so, for example, if all the collections would use the same reduce function, it can be defined outside the hierarchy as a free function, but the user would still be able to use collection.reduce as if it was part of the interface. another possibility is to use mixins for common functionality to avoid re-inventing the wheel. in any way, the wanted result is that the user can use a consistent API. -- YigalI don't know how Andrei implemented it, but either the template inlines stuffIf the function is called N times (where N > 0), the template is instantiated exactly once. The function /may/ get inlined, but this will be true of all functions, not just template functions. In D, there is no way to say "never inline this function".if not than it would be the same as the delegate version.Yes.only it'll bloat the executable since for each unique delegate there will be a copy of the template.It is certainly possible to ensure that there is exactly one delegate type for each element type, so the number of copies can be made minimal.If my understanding here is wrong, feel free to correct me. [Janice?]See separate post. By my reckoning, your OrderedCollection interface results in more executable bloat than use of std.algorithm.sort. (But if my understanding of your idea is wrong, feel free to correct me).
May 11 2008
Yigal Chripun Wrote:the idea is of course take the best from each language and combine it to something even better. BTW, you really should take a look at Eiffel. for example, it has multiple-inheritance implemented in a completely different way than C++ avoiding most of c++ problems with it. they have many novel ideas there that D can and should learn from. My perfect collection framework would be based on the Eiffel design, avoiding problems in the Java model and problems in the C++ model.It is fortunate in my humble opinion that the designers of D think otherwise. In my experience Eiffel is the worst of languages. I worked with Eiffel one project. It was very unsafe for a compiled language. It has covariant function arguments and covariant arrays! At least Java and D made only one mistake not two. It has problems of dynamic languages and problems of static languages but not many advantages of them. It is not surprising Eiffel is considered bad by my professors and also by people in the industry. Dee Girl
May 11 2008
Dee Girl wrote:Yigal Chripun Wrote:I was only referring to Eiffel's collection framework which i think is a good design. I noted that Eiffel has a different implementation of MI than c++ but that doesn't mean i want MI in D. I like the current design of interfaces and mixins in D instead of MI. also, I'm familiar with the covariance issues in Eiffel. I also do not like it's strange(IMO) syntax. fact is nothing is perfect and D does already borrow some concepts from Eiffel like DBC. sure, Eiffel has it problems, all languages has problems, and the industry (or your professors for that matter) are irrelevant since there are hundreds of different languages and non of which is the dominant one. this only shows that there are many ways and styles for programing and there are personal preferences involved. your professor may prefer one solution while mine prefers another, both can be valid. there are many academic languages that are ignored by the industry while it prefers using different languages. would you consider cobol a good language? the only objective way I know of to measure success of programming languages is the size of the beard of the language creators. there's a thread with a link to an article about that in the NG. anyway, back to topic. I was talking about Eiffel's collection framework and I was saying we should take the best from each language, meaning that specific initial design which could be improved since it's very flexible but can be complex for users since Eiffel uses MI and doesn't have anything like interfaces. D can take the flexibility of the design and improve it's user friendliness. of course, this entire post is my opinion only. feel free to disagree. also, off topic, have you ever heard of the programming language Icon?the idea is of course take the best from each language and combine it to something even better. BTW, you really should take a look at Eiffel. for example, it has multiple-inheritance implemented in a completely different way than C++ avoiding most of c++ problems with it. they have many novel ideas there that D can and should learn from. My perfect collection framework would be based on the Eiffel design, avoiding problems in the Java model and problems in the C++ model.It is fortunate in my humble opinion that the designers of D think otherwise. In my experience Eiffel is the worst of languages. I worked with Eiffel one project. It was very unsafe for a compiled language. It has covariant function arguments and covariant arrays! At least Java and D made only one mistake not two. It has problems of dynamic languages and problems of static languages but not many advantages of them. It is not surprising Eiffel is considered bad by my professors and also by people in the industry. Dee Girl
May 11 2008
Janice Caron, el 9 de mayo a las 09:15 me escribiste:(*) Use RTTI to cast up and down the class heirarchy. In C++, this would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the cast: class!(T)(x) to do that actual upcast or downcast - but RTTI dynamic casts can fail, so the question arises: What should we do if the cast fails? Should we return null, or should we throw an exception. My view is that we should throw an exception, but also introduce a new construct to test whether or not the cast would be possible in the first place: is!(T)(x)I don't think it's a good idea, because you end up doing the RTTI check twice this way. I think class!(T)(x) (I don't like the name but that's another issue) should return null in case of failure. If you want it to throw an exception, just wrap it with enforce[1]: enforce!(class!(T)(x)) This have been discussed recently, but this way it's more efficient (and I think more clear and less typing): void f(A a) { if (B b = class!(B)(a)) { /*...*/ } else if (C c = class!(C)(a)) { /*...*/ } } [1] http://www.digitalmars.com/d/2.0/phobos/std_contracts.html(*) Reinterpret a bit pattern. In C++, this would be reinterpret_cast<T>(x). Without changing the memory layout, or the constancy, reinterpret the bits to mean something else. For D, I suggest union!(T)(x) (*) Change const to mutable, or invariant to mutable. In C++, this would be const_cast<T>(x). There is no equivalent in D, however, in D, one can currently write cast(T)x, and constancy will be magically (and dangerously) waved away. In the new scheme, I suggest: auto!(T)(x)I think this 2 can be merged toguether and called horrible_cast in honour to Don Clugston's "Member Function Pointers and the Fastest Possible C++ Delegates" article [2] =) [2] http://www.codeproject.com/KB/cpp/FastDelegate.aspx -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ ---------------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------------- For me to ask a woman out, I've got to get into a mental state like the karate guys before they break the bricks. -- George Constanza
May 12 2008