Consistency, Templates, Constructors, and D3
Nick Treleaven
nospam at example.net
Fri Aug 24 09:27:42 PDT 2012
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.
More information about the Digitalmars-d
mailing list