On 24/08/2012 06:14, F i L wrote:
DISCLAIMER: This isn't a feature request or anything like that. It's
ONLY intended to stir _constructive_ conversation and criticism of D's
existing features, and how to improve them _in the future_ (note the
'D3' in the title).
To start, let's look at: cast(T) vs to!T(t)
In D, we have one way to use template function, and then we have special
keyword syntax which doesn't follow the same syntactical rules. Here,
cast looks like the 'scope()' or 'debug' statement, which should be
followed by a body of code, but it works like a function which takes in
the following argument and returns the result. Setting aside the
"func!()()" syntax for a moment, what cast should look like in D is:
int i = cast!int(myLong);
That syntax makes sense, but cast is a built-in language feature. I'm
not sure making it look like a library function is really worth the
change IMO. In some cases the syntax would be a bit noisier:
cast(immutable int) v; // current
cast!(immutable int)(v); // new
It's a similar story with __traits(). What appears to be a function
taking in a run-time parameter is actually compile-time parameter which
works by "magic". It should look like:
bool b = traits!HasMember(Foo);
Or:
bool b = traits.hasMember!(Foo, "bar")();
Also:
int i;
bool b = __traits(isArithmetic, i); // current
bool b = traits.isArithmetic(i); // new
'i' cannot be a compile-time parameter or a runtime parameter either (by
normal rules). So I think __traits are special, they're not really like
a template function.
# Nimrod code
template foo(x:int) # compile time
when x == 0:
doSomething()
else:
doSomethingElse()
proc bar(x:int) # run time
if x == 0:
doSomething()
else:
doSomethingElse()
block main:
foo(0) # both have identical..
bar(0) # ..call signatures.
In D, that looks like:
void foo(int x)() {
static if (x == 0) { doSomething(); }
else { doSomethingElse(); }
}
void bar(int x) {
if (x == 0) { doSomething(); }
else { doSomethingElse(); }
}
void main() {
foo!0();
bar(0); // completely difference signatures
}
Ultimately foo is just more optimized in the case where an 'int' can be
passed at compile time, but the way you use it in Nimrod is much more
consistent than in D. In fact, Nimrod code is very clean because there's
no special syntax oddities, and that makes it easy to follow (at least
on that level), especially for people learning the language.
Personally I think it's a benefit that D makes compile-time and runtime
parameters look different in caller code. The two things are very different.
But I think there's a much better way. One of the things people like
about Dynamicly Typed languages is that you can hack things together
quickly. Given:
function load(filename) { ... }
the name of the parameter is all that's required when throwing something
together. You know what 'filename' is and how to use it. The biggest
problem (beyond efficiency), is later when you're tightening things up
you have to make sure that 'filename' is a valid type, so we end up
having to do the work manually where in a Strong Typed language we can
just define a type:
function load(filename)
{
if (filename != String) {
error("Must be string");
return;
}
...
}
vs:
void load(string filename) { ... }
but, of course, sometimes we want to take in a generic parameter, as D
programmers are fully aware. In D, we have that option:
void load(T)(T file)
{
static if (is(T : string))
...
else if (is(T : File))
...
}
Perhaps I'm being pedantic, but here it would probably be:
void load(T:string)(T file){...}
void load(T:File)(T file){...}
but it's wonky. Two parameter sets? Type deduction? These concepts
aren't the easiest to pick up, and I remember having some amount of
difficulty first learn what the "func!(...)(...)" did in D.
I think it's straightforward if you already know func<...>(...) syntax
from C++ and other languages.
So why not have one set of parameters and allow "typeless" ones which
are simply compile-time duck-typed?
void load(file)
{
static if (is(typeof(file) : string))
...
else if (is(typeof(file) : File))
...
}
this way, we have one set of rules for calling functions, and
deducing/defaulting parameters, with the same power. Plus, we get the
convenience of just hacking things together and going back later to
tighten things up.
The above code doesn't look much easier to understand than the current D
example. I think knowing about template function syntax is simpler than
knowing how to do static if tests.
I think there was a discussion about this before, but using:
void load(auto file)
Your analysis seems to go into more depth than that discussion AFAIR.
...
Revisiting the cast()/__traits() issue. Given our new function call
syntax, they would looks like:
cast(Type, value);
traits(CommandEnum, values...);
Those do now look nice and consistent.
######### CONSTRUCTORS ##########
We're all aware that overriding new/delete in D is a depreciated
feature.
I thought it was a deprecated feature (SCNR ;-))
I agree with this, however I think we should go a step further
and remove the new/delete syntax all together... :D crazy I know, but
hear me out.
I have wondered whether 'new' will need to be a library function when
allocators are introduced.