Metthias Becker
| Don't get the folowing wrong. I just read through the whole D definition and wrote down, what comes up to my mine. That's why it became pretty long. It's intended to be a constructive critique.
(quote)
The reality of the C++ compiler business is that few compilers effectively
implement the entire standard.
(/quote)
That's true. The only compiler I know is Comeau, but there are many others like gcc that come close enough to be usefull. And when it's true, what I've heard about the new Borland compiler it seems we get another very close or even perfect compiler.
(quote)
Can the power and capability of C++ be extracted, redesigned, and recast into a
language that is simple, orthogonal, and practical? Can it all be put into a
package that is easy for compiler writers to correctly implement, and which
enables compilers to efficiently generate aggressively optimized code?
(/quote)
You don't answer the questions.
BTW: Is it realy possible to create something as powerfull as boost::lambda with
D? I don't know D very good, so I realy don't know, but I don't think so.
(quote)
The general look of D is like C and C++. This makes it easier to learn and port
code to D. Transitioning from C/C++ to D should feel natural, the programmer
will not have to learn an entirely new way of doing things.
(/quote)
A C coder has to learn the objectorientated paradigm. So it's not easy to learn. A C++ coder is used to have the powerfull STL and nowadays the Boost library, but there is no pendant in D. So it doesn't feel natural for a C++ coder, eigther.
E.g. a C++ coder want to copy the content of an container into a file he'd write:
copy (container.begin(), container.end(), ostream_iterator<Type>(streamhandle,
delimiter));
You could use the same construct to copy a container into another one or to copy a file to container, ... .
In D you have to do this totaly differently.
If you'd have to write prt<int> to create an int-pointer, it would still feel
natural to a c++ coder. It's the big things you got used to that matter, not
these small things.
You talk about the same look and feel. Let's take some C++-code and you sow me
how to port that while ceeping the same look and feel ( it's only a small part,
that's missing a lot of other headers ):
(c++ code)
template <
class methode_type_policy,
class check_policy = do_nothing_policy
>
class setgetter {
public: // Types
typedef typename methode_type_policy::value_type value_type;
typedef typename methode_type_policy::param_type param_type;
typedef typename methode_type_policy::return_type return_type;
typedef typename methode_type_policy::setter_type setter_type;
typedef typename methode_type_policy::getter_type getter_type;
typedef typename methode_type_policy::class_type * pointer;
public: // C'tors
explicit setgetter (pointer object=0, setter_type setter_methode=0, getter_type
getter_methode=0)
: m_this(object)
, m_setter(setter_methode)
, m_getter(getter_methode)
{ }
setgetter (const setgetter & other)
: m_this(other.m_this)
, m_setter(other.m_setter)
, m_getter(other.m_getter)
{ }
public: // Methodes
const setgetter & operator = (const setgetter & other)
{
m_this = other.m_this;
m_setter = other.m_setter;
m_getter = other.m_getter;
}
void operator = (param_type value)
{
check_policy::check(m_this);
check_policy::check(m_setter);
(m_this->*m_setter)(value);
}
operator return_type () const
{
check_policy::check(m_this);
check_policy::check(m_getter);
return (m_this->*m_getter)();
}
private:
pointer m_this;
setter_type m_setter;
getter_type m_getter;
};
(/c++ code)
Perhaps for a Java coder it feels "natural", but he/she will miss the reflection ability.
(quote)
D programs can be written either in C style function-and-data or in C++ style
object-oriented, or any mix of the two.
(/quote)
Well, C++'s oo-features a pretty limited. I'd expect more from a modern
programming language, but OK.
(quote)
Runtime Type Identification. This is partially implemented in C++; in D it is
taken to its next logical step. Fully supporting it enables better garbage
collection, better debugger support, more automated persistence, etc.
(/quote)
Well, Java took it to the next logical step. You did something inbetween.
(quote)
Templates. Templates are a way to implement generic programming. Other ways
include using macros or having a variant data type. Using macros is out.
Variants are straightforward, but inefficient and lack type checking. The
difficulties with C++ templates are their complexity, they don't fit well into
the syntax of the language, all the various rules for conversions and
overloading fitted on top of it, etc. D offers a much simpler way of doing
templates.
(/quote)
It's true that c++'s template syntax doesn't fit very well into the language. As well it's true that macros are out. But I don't agree on the variant types. It's true for stupid variants that some BASICs offer, but if you've ever coded in Objective-C you'd know how powerfull and efficient variant types can be.
(quote)
Features To Drop
[...]
The C preprocessor. Macro processing is an easy way to extend a language, adding
in faux features that aren't really there (invisible to the symbolic debugger).
Conditional compilation, layered with #include text, macros, token
concatenation, etc., essentially forms not one language but two merged together
with no obvious distinction between them. Even worse (or perhaps for the best)
the C preprocessor is a very primitive macro language. It's time to step back,
look at what the preprocessor is used for, and design support for those
capabilities directly into the language.
(/quote)
It's exactly, what I think. But then some of the Cuj-articles (www.cuj.com) come
to my mind and I doubt what I should think. There the preprocessor is used for
some realy tricky stuff. If you haven't read these articels you don't know, what
the preprocessor is realy used for. Perhaps you should read some of them and
than extend the language a bit so you realy can do the things you can with the
preprocessor without it.
(quote)
Namespaces. An attempt to deal with the problems resulting from linking together
independently developed pieces of code that have conflicting names. The idea of
modules is simpler and works much better.
(/quote)
Both ideas can coexist perfectly. Namespaces are much more flexibel and let you
structure your code, while D's modules are there to prevent you from name
conflicts.
(quote)
Who D is For
[...]
Programmers who enjoy the expressive power of C++ but are frustrated by the need
to expend much effort explicitly managing memory and finding pointer bugs.
(/quote)
All I can say is: smart pointers.
I still like a gc better.
(quote)
Programmers who enjoy the expressive power of C++ but are frustrated by the need
to expend much effort explicitly managing memory and finding pointer bugs.
(/quote)
But C++'s power comes only from it's template abilitys. As D's abilitys concerning that are realy limited you don't get C++'s power. How to do policy based design in D? How to write as wildly usable code as the STL-algorithms? How to create usefull tools like boost::bind, boost::lambda or boost::tuple? How to write as general code as Alexandrescu's Loki (http://moderncppdesign.com)?
(quote)
There is no longer a need for a separate definition of member functions, static
members, externs, nor for clumsy syntaxes like:
(/quote)
I totaly agree on static members and the extern stuff, but most languages point their ability of seperating the interface from the implementation as a feature not as a drawback. OK, C++'s way is realy clumsy, but other languages like Objective-C does it realy convenient.
(quote)
Associative Arrays
Associative arrays are arrays with an arbitrary data type as the index rather
than being limited to an integer index. In essence, associated arrays are hash
tables. Associative arrays make it easy to build fast, efficient, bug-free
symbol tables.
(/quote)
Nothing to complain about that, but most C++ librarys ship with a collection of
different hashs. Normaly it's based on ehte Sgi-version ( www.sgi.com/tech/stl
). So you have hash-sets as well as hash-maps both in a multi-version, too (the
same key may occure more than once). Perhaps you should extend D's abilitys a
bit.
And there are other associative containers like trees, that aren't avilable for
D. But trees are rarly used, so it's not a that big drawback.
(quote)
Function Literals
Anonymous functions can be embedded directly into an expression.
(/quote)
Normaly these things are cald lambda-expressions.
(quote)
Cast Expressions
In C and C++, cast expressions are of the form:
(type) unaryexpression
There is an ambiguity in the grammar, however. Consider: (foo) - p;
Is this a cast of a dereference of negated p to type foo, or is it p being
subtracted from foo? This cannot be resolved without looking up foo in the
symbol table to see if it is a type or a variable. But D's design goal is to
have the syntax be context free - it needs to be able to parse the syntax
without reference to the symbol table. So, in order to distinguish a cast from a
parenthesized subexpression, a different syntax is necessary.
C++ does this by introducing:
dynamic_cast(expression)
which is ugly and clumsy to type. D introduces the cast keyword:
cast(foo) -p; cast (-p) to type foo
(foo) - p; subtract p from foo
cast has the nice characteristic that it is easy to do a textual search for it,
and takes some of the burden off of the relentlessly overloaded () operator.
(/quote)
In C++ there is a static_cast<Type>(expression), const_cast<Type>(expression),
reinterpret_cast<Type>(expression) and the already mentioned
dynamic_cast<Type>(expression). Today nobody uses the C-casts any longer.
static_cast<foo>(-p) isn't ambingious.
And casts are clumsy, because you normaly shouldn't cast and the clumsy syntax protects you from casting more than needed.
(quote)
A block statement introduces a new scope for local symbols. A local symbol's
name, however, must be unique within the function.
(/quote)
Why?
So
for (int i=0; i<10; ++i)
foo;
for (int i=0; i<10; ++i)
bar;
is illegal? What about your sentence "Transitioning from C/C++ to D should feel natural, the programmer will not have to learn an entirely new way of doing things."?
Why are reverse and sort abilitys of arrays? shouldn't they be part of the library? Or is it realy much more efficient this way?
(quote)
Resizing a dynamic array is a relatively expensive operation. So, while the
following method of filling an array:
[... some code]
will work, it will be efficient
(/quote)
You mean it wont, not it will, don't you?
(quote)
str ~= "\0";
printf("the string is '%s'\n", (char *)str);
(/quote)
(char *) looks like a C-style cast. What is it?
(quote)
Enums replace the usual C use of #define macros to define constants. Enums can
be either anonymous, in which case they simply define integral constants, or
they can be named, in which case they introduce a new type.
(/quote)
Hu??? enum seems to be the same as enum in C and in C you'd use enum. So this is stupid.
(quote)
In C++, there are many complex levels of function overloading, with some defined
as "better" matches than others. If the code designer takes advantage of the
more subtle behaviors of overload function selection, the code can become
difficult to maintain. Not only will it take a C++ expert to understand why one
function is selected over another, but different C++ compilers can implement
this tricky feature differently, producing subtly disastrous results.
In D, function overloading is simple. It matches exactly, it matches with
implicit conversions, or it does not match. If there is more than one match, it
is an error.
(/quote)
While this is good in general there might be some places, where this can be a
disadventage. E.g Alexandrescu's Mojo (you can find a description in one of the
Cuj articles).
But I don't think this is a reason to change this. I just wanted to mention it.
(quote)
Parameters are in, out, or inout. in is the default; out and inout work like
storage classes. For example:
(/quote)
But why is there a keyword in? Isn't it more complicated for the compiler, because it has to dispatch, whether it is the in-operator or the in-modifier? I think there is no need for in in this case.
Hu? What's up with the folowing:
(quote)
It is an error to declare a local variable that hides another local variable in
the same function:
void func(int x)
{ int x; error, hides previous definition of x
double y;
..
{ char y; error, hides previous definition of y
int z;
}
{ wchar z; legal, previous z is out of scope
}
}
(/quote)
but before:
(quote)
void func3()
{
{ int x;
}
{ int x; // illegal, x is multiply defined in function scope
}
}
(/quote)
Sorry, but I don't understand that.
D's templates seem to be very complicated and somewhat limited compared to C++'s templates.
(c++ code)
template <typename T>
void foo (T bar)
{ ... }
double quer ()
{ ... }
foo(quer()); // the work is done by the compiler
(/c++ code)
versus
(D code)
template my_template (T) {
void foo (T bar)
{ ... }
}
double quer ()
{ ... }
instance my_template(double) my_instance;
my_instance.foo(quer);
(/D code)
If I got it right. Perhaps you should think about a way making it more easy.
About user defined memory allocation:
(quote)
- If new() cannot allocate memory, it must not return null, but must throw an
exception.
[...]
- A null is not returned if storage cannot be allocated. Instead, an exception
is thrown.
(/quote)
Where is the difference between these two points?
About Phobos' conv
Just an idea, but perhaps this should become a mupltiply specialised template. If D's template abilitys weren't that limited, I'd suggest something like boost::lexical_cast
(c++ code)
using namepsace std;
using namepsace boost;
string my_string = "123";
int foo = lexical_cast<int>(my_string);
string bar = lexical_cast<string>(foo);
(/c++ code)
About Phobos' instrinsic
why aren't the math functions instrinsic if possible and are removed in this part ot Phobos.
About Phobos' string
There are some functions like find, replace, insert or count, that you normaly
want to apply on all types of array, not only on char-arrays. Why are they that
limited?
From the FAQ:
(quote)
Why is [expletive deleted] printf in D?
printf is not typesafe. It's old fashioned. It's not object-oriented. It's not
usable with user-defined types. printf is guilty as charged. But it's just so
darned useful. Nothing beats it for banging out a quick dump of a value when
debugging.
(/quote)
Look at boost::format. It's typesafe, can be used with userdefined types, but
uses a printf-like formatstring. OK, you can't do this in D, because of the
limited templates, but if templates might be reviewed it might be an interesting
option.
(quote)
Why fall through on switch statements?
Many people have asked for a requirement that there be a break between cases in
a switch statement, that C's behavior of silently falling through is the cause
of many bugs.
The reason D doesn't change this is for the same reason that integral promotion
rules and operator precedence rules were kept the same - to make code that looks
the same as in C operate the same. If it had subtly different semantics, it will
cause frustratingly subtle bugs.
(/quote)
Well C# does it both. If it looks the same it has the same semantic, but there is no silent fall through. The last command before a case statement must be eigther break, return or goto. With goto you can simulate the fall through.
(pseudocode)
switch (foo) {
case 'a':
goto case 'A';
case 'A':
do_something();
break;
case 'b':
goto case 'B';
case 'B':
return bar;
default:
something_else();
}
(/pseudocode)
(quote)
Doesn't C++ support strings, bit arrays, etc. with STL?
In the C++ standard library are mechanisms for doing strings, bit arrays,
dynamic arrays, associative arrays, bounds checked arrays, and complex numbers.
Sure, all this stuff can be done with libraries, following certain coding
disciplines, etc. But you can also do object oriented programming in C (I've
seen it done). Isn't it incongruous that something like strings, supported by
the simplest BASIC interpreter, requires a very large and complicated
infrastructure to support? Just the implementation of a string type in STL is
over two thousand lines of code, using every advanced feature of templates. How
much confidence can you have that this is all working correctly, how do you fix
it if it is not, what do you do with the notoriously inscrutable error messages
when there's an error using it, how can you be sure you are using it correctly
(so there are no memory leaks, etc.)?
D's implementation of strings is simple and straightforward. There's little doubt how to use it, no worries about memory leaks, error messages are to the point, and it isn't hard to see if it is working as expected or not. (/quote)
This is only a paritial answer. You only anwer the string-part and you forget that STL-strings are much more flexible, becuase they accept user defined policys. OK in general case you don't need them, but than you can use the typedef std::string instead of std::basic_string.
And how can I know, that the D compiler I use is perfect? I have to hope it does, as I have to hope that my STL-implementation does. And if my STL-implementation has a bug I could fix it myself until there is a official bugfix, but what about a broken compiler?
And sorry, but the STL is just much more flexible than D's datastructures are.
In D I have dynamic array and one type of hash. The STL has a dynamic array, a
double linked list, something inbetween called double ended quque, four types of
trees, and almoast all implementations have four additional
hash-implementations. And than there are adapters like a stack, a quque and a
priority quque. SGi's implementation (that is wildly used, STLport is based on
it) additionaly has a singly linked list.
These are all datastructures I use in everydays business. They aren't something
extraordinary.
And who cares about how many lines the sting implementations has. Maybe you've written 5000 lines in you D-compiler for it. Who knows? And who is realy interested in it?
You can say everything about C++, it has a ugly, clumsy syntax or whatever, but the STL is the best library for these proposes, ever.
(quote)
Can't unit testing be done in C++ with an add-on library?
Sure. Try one out and then compare it with how D does it. It'll be quickly
obvious what an improvement building it into the language is.
(/quote)
Are there realy people that asked this? I think it is pretty obvious that the
build in tests have big adventages. That is one of the things I realy like in D.
That's why I thought about switching to D, but I'd miss the STL, many of the
boost-features and the MUCH more powerfull templates too much.
-> Example: wc
Why do you change your style all the time?
In the first for-loop use write c++-coder-like ++i in the second one you write
j++ like a coder of another language would do.
Sometimes you leave lines beginning with { free, sometimes you declare variables
in them.
Sometimes you directly initialice variables somtimes you split it up into two
lines:
char[] word = input[wstart .. input.length];
but
char[] word;
word = keys[i];
This is realy strange code. It looks like it was written by two totaly different
coders. Perhaps you should review your code.
Some small changes like the following code would be enough to give it a general
look and feel (while that is still far away from how I'd do it):
(D code)
import stdio;
import file;
int main (char[][] args)
{
int w_total;
int l_total;
int c_total;
int[char[]] dictionary;
printf(" lines words bytes file\n");
for (int i = 1; i < args.length; ++i)
{
int w_cnt, l_cnt, c_cnt;
int inword;
int wstart;
char[] input = File.read(args[i]);
for (int j = 0; j < input.length; ++j)
{
char c = input[j];
if (c == "\n")
++l_cnt;
if (c >= "0" && c <= "9")
{
}
else if (c >= "a" && c <= "z" ||
c >= "A" && c <= "Z")
{
if (!inword)
{
wstart = j;
inword = 1;
++w_cnt;
}
}
else if (inword)
{
char[] word = input[wstart .. j];
++dictionary[word];
inword = 0;
}
++c_cnt;
}
if (inword)
{
char[] word = input[wstart .. input.length];
++dictionary[word];
}
printf("%8lu%8lu%8lu %s\n", l_cnt, w_cnt, c_cnt, (char *)args[i]);
l_total += l_cnt;
w_total += w_cnt;
c_total += c_cnt;
}
if (args.length > 2)
{
printf("--------------------------------------\n%8lu%8lu%8lu total",
l_total, w_total, c_total);
}
printf("--------------------------------------\n");
char[][] keys = dictionary.keys;
for (int i = 0; i < keys.length; i++)
{
char[] word = keys[i];
printf("%3d %.*s\n", dictionary[word], word);
}
return 0;
}
(/D code)
Finaly I'd like to say that D is missing the realy new things. If you like design by contract, you could switch to Eiffel or Sather, which both have in addition a much more convenient notation of generic code (templates). Translated to D it would be something like:
(pseudocode)
class My_class [T] {
T foo;
T bar (T value)
{ ... }
}
..
My_Class[int] my_instance;
My_Class[double] another_instance;
(/pseudocode)
This would perfectly fit into D as D's hashes use the same syntax. And you could implement it this way: You only create one type of My_class using Object for template-parameters. The explicit type is only used by the compiler to implicitly insert casts where needed. This way you don't waste any memory, and everything is typesafe.
To get back to the topic: D isn't as flexible as Objective-C or Samlltalk, it isn't as portable as Java, it isn't as powerfull as C++, it doesn't have a big company behind it like C# ;) nor is it as easy to learn as Object Pascal. And for small automizations D is too complex, so you'd prefer a scriptlanguage like Phyton or Ruby. So I don't see where to use D.
|