|
Archives
D Programming
D
D.gnu
digitalmars.D
digitalmars.D.bugs
digitalmars.D.dtl
digitalmars.D.ide
digitalmars.D.dwt
digitalmars.D.announce
digitalmars.D.learn
digitalmars.D.debugger
C/C++ Programming
c++
c++.announce
c++.atl
c++.beta
c++.chat
c++.command-line
c++.dos
c++.dos.16-bits
c++.dos.32-bits
c++.idde
c++.mfc
c++.rtl
c++.stl
c++.stl.hp
c++.stl.port
c++.stl.sgi
c++.stlsoft
c++.windows
c++.windows.16-bits
c++.windows.32-bits
c++.wxwindows
digitalmars.empire
digitalmars.DMDScript
electronics
|
digitalmars.D - Safer casts
↑ ↓ ← → "Janice Caron" <caron800 googlemail.com> writes:
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?
↑ ↓ ← → terranium <spam here.lot> writes:
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 invariance
1) 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.
↑ ↓ ← → terranium <spam here.lot> writes:
Janice Caron Wrote:
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); // 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.
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.
I wasn't disputing that. Consider this a separate proposal.
↑ ↓ ← → terranium <spam here.lot> writes:
and I like existing cast keyword, it's short enough to write and long enough to
spot.
↑ ↓ ← → terranium <spam here.lot> writes:
And I hate those exclamation marks and chains of brackets.
↑ ↓ ← → "Janice Caron" <caron800 googlemail.com> writes:
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); // OK
D 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.
↑ ↓ ← → "Janice Caron" <caron800 googlemail.com> writes:
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
↑ ↓ ← → janderson <askme me.com> writes:
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
↑ ↓ ← → Robert Fraser <fraserofthenight gmail.com> writes:
janderson wrote:
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
I think Janice mentioned that it would work exactly like that.
↑ ↓ ← → "Janice Caron" <caron800 googlemail.com> writes:
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.
↑ ↓ ← → BCS <BCS pathlink.com> writes:
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).
↑ ↓ ← → BCS <BCS pathlink.com> writes:
Janice Caron wrote:
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.
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.
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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.
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
Janice Caron Wrote:
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)bar
Your 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
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 Girl
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
Janice Caron Wrote:
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++.
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 Girl
↑ ↓ ← → "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
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)bar
For 3), what if I want to cast const(int*) to const(int)*?
cast(const(int)*)cast(!const)foo?
-- Simen
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Simen Kjaeraas wrote:
For 3), what if I want to cast const(int*) to const(int)*?
cast(const(int)*)cast(!const)foo?
-- Simen
that looks correct.
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Janice Caron wrote:
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:
1) convert or downcast: cast(Foo)bar
2) reinterpret: cast!(Foo)bar
3) constancy: cast(invariant)bar, cast(!const)bar
cast(const(int)*)cast(!const)foo?
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.
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.
--Yigal
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Janice Caron Wrote:
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.
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?
--Yigal
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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.
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.
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.
--Yigal
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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;
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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.
↑ ↓ ← → "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Simen Kjaeraas" wrote
On Sat, 10 May 2008 07:59:23 +0200, Janice Caron wrote:
1) convert or downcast: cast(Foo)bar
2) reinterpret: cast!(Foo)bar
3) constancy: cast(invariant)bar, cast(!const)bar
For 3), what if I want to cast const(int*) to const(int)*?
cast(const(int)*)cast(!const)foo?
-- Simen
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.
-Steve
↑ ↓ ← → "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
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.
-Steve
Actually, 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
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Simen Kjaeraas wrote:
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.
-Steve
Actually, 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
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)
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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
↑ ↓ ← → terranium <spam here.lot> writes:
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.
↑ ↓ ← → terranium <spam here.lot> writes:
Janice Caron Wrote:
If you want to talk Unicode
I'll let others to have the pleasure to discover this themselves :)))
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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
from: java, C#, python, ruby, c/c++, etc. Tango is a good example of
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
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
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
from: java, C#, python, ruby, c/c++, etc. Tango is a good example of
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
↑ ↓ ← → Sean Kelly <sean invisibleduck.org> writes:
== Quote from Dee Girl (deegirl noreply.com)'s article
What 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
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
Sean Kelly Wrote:
== Quote from Dee Girl (deegirl noreply.com)'s article
What 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
Nice example! How did you do it? Did Tango change the compiler and added more
methods to arrays? Thank you, Dee Girl
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Janice Caron wrote:
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 Girl
Yeah, 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" ]);
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.
-- Yigal
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
Yigal Chripun Wrote:
Janice Caron wrote:
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 Girl
Yeah, 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" ]);
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.
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 Girl
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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
Girl
I'm not sure what exactly are you asking. are you asking what's the
difference between a delegate and a function?
--Yigal
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
Yigal Chripun Wrote:
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
Girl
I'm not sure what exactly are you asking. are you asking what's the
difference between a delegate and a function?
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 Girl
↑ ↓ ← → BCS <ao pathlink.com> writes:
Reply to Dee,
Yigal Chripun Wrote:
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
Girl
difference between a delegate and 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 Girl
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 <deegirl noreply.com> writes:
BCS Wrote:
Reply to Dee,
Yigal Chripun Wrote:
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
Girl
difference between a delegate and 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 Girl
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.
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:
#!/home/yasuko/bin/compile-launch -w
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 Girl
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
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
↑ ↓ ← → "Koroskin Denis" <2korden gmail.com> writes:
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.
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Koroskin Denis wrote:
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.
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.
--Yigal
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Janice Caron wrote:
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.
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?
--Yigal
↑ ↓ ← → Leandro Lucarella <llucax gmail.com> writes:
Janice Caron, el 11 de mayo a las 12:53 me escribiste:
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.
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)
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Leandro Lucarella wrote:
Janice Caron, el 11 de mayo a las 12:53 me escribiste:
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.
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.
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
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.
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
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
↑ ↓ ← → Yigal Chripun <yigal100 gmail.com> writes:
Please read my response to Bill.
Dee Girl wrote:
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?
But it doesn't. also, the file name and line number are wrong. that IS
one of my problems with this.
what if i typed n instead of b (they are next to each other on my
keyboard)?
Same message is written.
again, The error I get points me to the wrong place with the wrong error.
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.
should I repeat the above, 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 ^_^
oh, god! finally! (that's my inner atheist talking ;) )
and yet, I still need to type that q which is meaningless to me.
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.
code is read 100 times more then it's written you need to type more
"once" but I get to read it many times.
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.
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.
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.
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 have
. It is the best writing of the intent of the sort. But I do not
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.
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 ^_^
Agreed.
"first make sure the program is correct, then make sure it's fast"
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.
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.
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
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?
↑ ↓ ← → Dee Girl <deegirl noreply.com> writes:
Yigal Chripun Wrote:
Please read my response to Bill.
Dee Girl wrote:
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?
But it doesn't. also, the file name and line number are wrong. that IS
one of my problems with this.
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 ^_^
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.
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.
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.
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.
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.
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.
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 understand what you say. Then you have option to specify type if you want.
But some other times (maybe more often) you prefer generic.
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
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);
I looked in std.functional and this is exactly how it works ^_^
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.
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 ^_^
Agreed.
"first make sure the program is correct, then make sure it's fast"
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.
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.
I do not understand. Can you please write a bit of code that would be better
than std.sort?
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
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.
I am so sorry!! I used a wrong word. It was meant as spending time on
explaining instead of working on my project. Sorry!
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?
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
↑ ↓ ← → Bill Baxter <dnewsgroup billbaxter.com> writes:
Yigal Chripun wrote:
Leandro Lucarella wrote:
Janice Caron, el 11 de mayo a las 12:53 me escribiste:
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.
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.
It justs do lexical analisys, but it's something:
sort!(q{a > b})(array);
[1] http://www.digitalmars.com/d/2.0/lex.html
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"?
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 |