www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - My thoughts & experiences with D so far, as a novice D coder

reply "Vidar Wahlberg" <vidar.wahlberg gmail.com> writes:
I know I'm probably going to upset some people with this, bashing 
their favourite child and all, but I wanted to let you know the 
experience I've had with D so far, as a novice D coder with a 
heavy Java & light C++ background.
It's not that I dislike D, in fact there are tons of things I 
love about it, it's pretty much exactly what I'm looking for in a 
programming language at the moment. Yet, I encounter some 
frustrating issues when coding, often leaving me with the 
impression that I'm fighting the language more than the problem 
I'm trying to solve.
True, there are many things I don't know about D, compilers or 
the inner workings of a computer, and some of the fights I have 
with the language are likely started by myself because I'm 
dragging along my bias from other languages, drawing 
misconceptions on how the D language actually works.
My intentions are not to insult, but shed some light on some of 
the difficulties I've faced (and how I solved them), with the 
hope that it will help others from facing the same difficulties.


Woes:
-----
- I find myself in a world of pain when I want to share data more 
complex than the basic data types (int, char, byte, etc) across 
threads. Seemingly the magic trick is to "cast(shared) foo" (or 
"cast(immutable)") when passing objects/references to another 
thread, then "cast(Foo)" back on the receiving end (as most 
classes/structs in the standard library refuse to let you call 
any methods when the object is shared). The examples in the 
source and TDPL are fairly limited on the issue, it mostly covers 
only those basic data types.

- While the "auto"-keyword often is great, it can lead to 
difficulties, especially when used as the return type of a 
function, such as "auto foo() { return bar; }". Sometimes you may 
wish to store the result of a function/method call as a global 
variable/class member, but when the function/method returns 
"auto" it's not apparent what the data type may be. While you may 
be able to find out what "bar" is by digging in the source code, 
it can still be difficult to find. One example is to save the 
result of "std.regex.match()" as a member in a class. For me the 
solution was to "import std.traits", create a function "auto 
matchText(string text) { return match(text, myRegex); }" and 
define the class member as "ReturnType!matchText matchResult;" 
(do also note that function & member must come in the right order 
for this to compile). This was all but obvious to a novice D 
coder as myself, the solution was suggested to me in the IRC 
channel.


Gotchas:
--------
- The lack of "rectangular" arrays created at runtime in D ("int 
i = 5; int[i][i] foo;") can be quite confusing for programmers 
with Java or C++ background. Even though there exists 
alternatives 
(http://denis-sh.github.com/phobos-additions/unstd.multidimensionalarray.html), 
this design decision and how to get around it when you really 
desire a "rectangular" array could be explained in more detail at 
http://dlang.org/arrays.html.

- Static array versus dynamic array was one of the first traps I 
stepped on 
(http://forum.dlang.org/thread/jnu1an$rjr$1 digitalmars.com). 
Until Jonathan M. Davis explained it in detail 
(http://d.puremagic.com/issues/show_bug.cgi?id=8026#c4), I pretty 
much considered it as "magic" that "randomShuffle(staticArray);" 
did not sort the array while "randomShuffle(staticArray[]);" did 
(the first call now gives you an compile error, though). That 
static arrays are value types while dynamic arrays are reference 
types may not be obvious for those with primarily Java background.

- When casting a value to an enum, there's no checking that the 
value actually is a valid enum value. Don't think I ever found a 
solution on how to check whether the value after casting is a 
valid enum value, it hasn't been a pressing issue.

- Compiling where DMD can't find all modules cause a rather 
cryptic error message. A solution is to make sure you specify all 
source files when compiling.


Wishlist:
---------
- "void[T]" associative array (i.e. a "set") would be nice, can 
be achieved with "byte[0][T]".

- "Foo foo = new Foo();" for global variables/class members. Now 
you must "Foo foo; static this() { foo = new Foo(); }".
Mar 27 2013
next sibling parent "John Colvin" <john.loughran.colvin gmail.com> writes:
On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg wrote:

 - The lack of "rectangular" arrays created at runtime in D 
 ("int i = 5; int[i][i] foo;") can be quite confusing for 
 programmers with Java or C++ background. Even though there 
 exists alternatives 
 (http://denis-sh.github.com/phobos-additions/unstd.multidim
nsionalarray.html), 
 this design decision and how to get around it when you really 
 desire a "rectangular" array could be explained in more detail 
 at http://dlang.org/arrays.html.
int i = 5; auto foo = new int[][](i,i);
Mar 27 2013
prev sibling next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Vidar Wahlberg:

 - While the "auto"-keyword often is great, it can lead to 
 difficulties, especially when used as the return type of a 
 function, such as "auto foo() { return bar; }". Sometimes you 
 may wish to store the result of a function/method call as a 
 global variable/class member, but when the function/method 
 returns "auto" it's not apparent what the data type may be. 
 While you may be able to find out what "bar" is by digging in 
 the source code, it can still be difficult to find.
Beside using returnType as suggested, another solution is to add in your code something like this: pragma(msg, typeof(foo)); This tells you the type to use for your class member. Haskell solves this in a better way, using "Type holes": http://www.haskell.org/haskellwiki/GHC/TypeHoles The idea of those holes was developed in several complex ways, but at its smallest it is just a way offered by the compiler to the Haskell programmer to leave one thing explicitly not specified. The program will not compile, but the error message will tell you very well the type of what's missing. So you use this type information to put in the hole what the compiler wants.
 That static arrays are value types while dynamic arrays are 
 reference types may not be obvious for those with primarily 
 Java background.
Java has a semantics more limited compared to a system language as D/Rust. This is not easy to avoid. On the other hand iterating on an array of structs/fixed size arrays has a trap that a D lint should warn against.
 - When casting a value to an enum, there's no checking that the 
 value actually is a valid enum value. Don't think I ever found 
 a solution on how to check whether the value after casting is a 
 valid enum value, it hasn't been a pressing issue.
cast() is a sharp unsafe tool. In Bugzilla I have a request to use to!() to perform that safely.
 - Compiling where DMD can't find all modules cause a rather 
 cryptic error message.
This was improved, and maybe there is further space for improvements. It's a fixable problem.
 Wishlist:
 ---------
 - "void[T]" associative array (i.e. a "set") would be nice, can 
 be achieved with "byte[0][T]".
I think there's no need to add that as a built-in. There are things much more important to have as builtins (like tuples). For that a Phobos Set!T suffices.
 - "Foo foo = new Foo();" for global variables/class members.
Maybe in some time it will happen, thanks to Don too. Bye, bearophile
Mar 27 2013
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 05:04:48PM +0100, bearophile wrote:
[...]
- When casting a value to an enum, there's no checking that the
value actually is a valid enum value. Don't think I ever found a
solution on how to check whether the value after casting is a
valid enum value, it hasn't been a pressing issue.
cast() is a sharp unsafe tool. In Bugzilla I have a request to use to!() to perform that safely.
[...] It already does that on latest git phobos: enum A { abc=100, def=200 } A a; a = to!A(150); // throws runtime exception T -- All problems are easy in retrospect.
Mar 27 2013
prev sibling parent reply "renoX" <renozyx gmail.com> writes:
On Wednesday, 27 March 2013 at 16:04:49 UTC, bearophile wrote:
 Vidar Wahlberg:
[cut]
 That static arrays are value types while dynamic arrays are 
 reference types may not be obvious for those with primarily 
 Java background.
Java has a semantics more limited compared to a system language as D/Rust. This is not easy to avoid.
Especially when keeping the poor C/C++ syntax for declaration instead of Pascal-style declaration syntax where you *name* the kind of array you're using! *Sigh* and some still think that syntax doesn't matter.. renoX
Mar 28 2013
parent reply "Vidar Wahlberg" <vidar.wahlberg gmail.com> writes:
To follow up with some new woes I'm currently struggling with:
I'm storing some various values in an ubyte array. I discovered 
that it's probably std.bitmanip I wish to use in order to 
"convert" i.e. an int to 4 bytes (although I went first to 
std.conv looking for this feature).
So I have "ubyte[] buffer;", and my second thought is that the 
"append" method 

want to append values to my ubyte-array (my first thought was 
something like "buffer ~= to!ubyte[](42);", although then I 
forgot about endianness). In the example in the documentation it 
does say "auto buffer = appender!(const ubyte[])();", with no 
explanation as of what "appender" is (I later learned that this 
is from std.array), but just looking a bit up I see that the 
"write" method explained just above use "ubyte[] buffer; 
buffer.write!ubyte(42);", so I assumed that I could use ubyte[] 
myself instead of this "appender" which I thought was some legacy 
code.
So I write some simple test code:
  import std.bitmanip, std.stdio;
  void main() {
   ubyte[] buffer;
   buffer.append!ubyte(42);
  }
Run it through rdmd, and get: 
"core.exception.AssertError /usr/include/d/std/array.d(591): 
Attempting to fetch the front of an empty array of ubyte".
Just to see what happens I set the size of the buffer 
("buffer.length = 1;") before appending and run it again. Now it 
runs, but instead of appending it behaves like write(), which was 
not exactly what I wanted.

At this time I google for this "appender" used in the example and 
learn that it comes from std.array, so I import std.array and try 
again using "auto buffer = appender!(ubyte[])();", and surely 
enough, now it does append correctly to the buffer. Great, I have 
a solution, so I go back to my project and implement it like I 
implemented it in my test code, but when I compile my project 
after this addition I get a new cryptic error message: "Error: 
__overloadset isn't a template".
After digging a bit I realized that it's because in my project I 
also import std.file, apparently there are some collisions 
between std.bitmanip and std.file. Again it's solvable, but it's 
yet another fight with the language/standard library. I would 
also assume that it's not that uncommon for a module that use 
std.bitmanip to also use std.file, meaning that this error 
potentially may occur often.

A bit on the side: It seems to me as importing std.bitmanip 
somehow adds new properties to my array (".read()" and 
".write()", for example). Not necessarily a bad thing, more of 
"I've not seen this before, I was expecting that I were to 
concatenate the bytes from the conversion to my buffer using ~".
Mar 28 2013
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Mar 28, 2013 at 09:24:49PM +0100, Vidar Wahlberg wrote:
 To follow up with some new woes I'm currently struggling with: I'm
 storing some various values in an ubyte array. I discovered that it's
 probably std.bitmanip I wish to use in order to "convert" i.e.  an int
 to 4 bytes (although I went first to std.conv looking for this
 feature).
[...] There are several ways to convert an int into 4 bytes: 1) Use a union: static assert(int.sizeof==4); ubyte[4] intToUbytes(int x) { union U { int i; ubyte[4] b; } U u; u.i = x; return u.b; } 2) Use bit operators: ubyte[4] intToUbytes(int x) { ubyte[4] bytes; // Note: this assumes little-endian. For big-endian, // reverse the order below. bytes[0] = x & 0xFF; bytes[1] = (x >> 8) & 0xFF; bytes[2] = (x >> 16) & 0xFF; bytes[3] = (x >> 24) & 0xFF; return bytes; } 3) Use a pointer cast (warning: un- safe): ubyte[4] intToUbytes(int x) system { ubyte[4] b; ubyte* ptr = cast(ubyte*)&x; b[0] = *ptr++; b[1] = *ptr++; b[2] = *ptr++; b[3] = *ptr; return b; } 4) Reinterpret a pointer (warning: un- safe): ubyte[4] intToUbytes(int x) system { return *cast(ubyte[4]*)&x; } I'm sure there are several other ways to do it. You don't need to use appender unless you're doing a lot of conversions in one go. --T
Mar 28 2013
prev sibling next sibling parent 1100110 <0b1100110 gmail.com> writes:
On 03/28/2013 03:24 PM, Vidar Wahlberg wrote:
 To follow up with some new woes I'm currently struggling with:
 I'm storing some various values in an ubyte array. I discovered that
 it's probably std.bitmanip I wish to use in order to "convert" i.e. an
 int to 4 bytes (although I went first to std.conv looking for this
 feature).
 So I have "ubyte[] buffer;", and my second thought is that the "append"

 want to append values to my ubyte-array (my first thought was something
 like "buffer ~= to!ubyte[](42);", although then I forgot about
 endianness). In the example in the documentation it does say "auto
 buffer = appender!(const ubyte[])();", with no explanation as of what
 "appender" is (I later learned that this is from std.array), but just
 looking a bit up I see that the "write" method explained just above use
 "ubyte[] buffer; buffer.write!ubyte(42);", so I assumed that I could use
 ubyte[] myself instead of this "appender" which I thought was some
 legacy code.
 So I write some simple test code:
 import std.bitmanip, std.stdio;
 void main() {
 ubyte[] buffer;
 buffer.append!ubyte(42);
 }
 Run it through rdmd, and get:
 "core.exception.AssertError /usr/include/d/std/array.d(591): Attempting
 to fetch the front of an empty array of ubyte".
 Just to see what happens I set the size of the buffer ("buffer.length =
 1;") before appending and run it again. Now it runs, but instead of
 appending it behaves like write(), which was not exactly what I wanted.

 At this time I google for this "appender" used in the example and learn
 that it comes from std.array, so I import std.array and try again using
 "auto buffer = appender!(ubyte[])();", and surely enough, now it does
 append correctly to the buffer. Great, I have a solution, so I go back
 to my project and implement it like I implemented it in my test code,
 but when I compile my project after this addition I get a new cryptic
 error message: "Error: __overloadset isn't a template".
 After digging a bit I realized that it's because in my project I also
 import std.file, apparently there are some collisions between
 std.bitmanip and std.file. Again it's solvable, but it's yet another
 fight with the language/standard library. I would also assume that it's
 not that uncommon for a module that use std.bitmanip to also use
 std.file, meaning that this error potentially may occur often.

 A bit on the side: It seems to me as importing std.bitmanip somehow adds
 new properties to my array (".read()" and ".write()", for example). Not
 necessarily a bad thing, more of "I've not seen this before, I was
 expecting that I were to concatenate the bytes from the conversion to my
 buffer using ~".
Yes, some functions do overload one another's functions... Usually it's because they are templated, and can accept the same arguments. using std.file.read will work, or import stdfile = std.file; stdfile.read; Just makin sure ya knew how to fix that.
Mar 28 2013
prev sibling next sibling parent reply "Chris Cain" <clcain uncg.edu> writes:
On Thursday, 28 March 2013 at 20:24:50 UTC, Vidar Wahlberg wrote:
 To follow up with some new woes I'm currently struggling with:
 I'm storing some various values in an ubyte array. I discovered 
 that it's probably std.bitmanip I wish to use in order to 
 "convert" i.e. an int to 4 bytes (although I went first to 
 std.conv looking for this feature).
 --snip--
I am completely confused as to why you're doing what you are doing ... std.conv does work (and in the case you've listed, is unnecessary anyway). Try this: import std.stdio, std.conv; void main() { ubyte[] buffer; buffer ~= 5; // Simple solution buffer ~= to!ubyte(6); // Proper usage of "to" writeln(buffer); } --- Now, for performance reasons you might want to use appender (or buffer.reserve(n), if you happen to know how many items you'll be storing), but the simplest thing works.
Mar 28 2013
parent "Vidar Wahlberg" <vidar.wahlberg gmail.com> writes:
On Thursday, 28 March 2013 at 21:16:32 UTC, Chris Cain wrote:
 I am completely confused as to why you're doing what you are 
 doing ... std.conv does work (and in the case you've listed, is 
 unnecessary anyway). Try this:

 import std.stdio, std.conv;

 void main() {
     ubyte[] buffer;
     buffer ~= 5; // Simple solution
     buffer ~= to!ubyte(6); // Proper usage of "to"
     writeln(buffer);
 }
This is not what I'm trying to achieve. This gives me an array with two elements, [5, 6]. What I want is to append the 4 bytes that make up one integer value, which using your values means buffer should hold a total of 8 bytes (two integers). H. S. Teoh answered well on how this can be achieved, although my feedback was not really meant as a question of "how is this done?", more of "why is this done like this, couldn't it be done much easier?".
Mar 28 2013
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Thursday, 28 March 2013 at 20:24:50 UTC, Vidar Wahlberg wrote:
 that it's probably std.bitmanip I wish to use in order to 
 "convert" i.e. an int to 4 bytes (although I went first to 
 std.conv looking for this feature).
Ah, I reread it a couple of times and realized what you mean now. You want to turn an int like 5 into an array of ubytes like [0,0,0,5]. So, you were on the right track, here's one way on how it's done: --- import std.stdio, std.bitmanip, std.array; void main() { auto app = appender!(ubyte[])(); // Create an appender of type ubyte[] app.append!int(5); writeln(app.data()); } --- Without using appender, it's a bit more complicated: --- import std.stdio, std.bitmanip; void main() { auto buf = new ubyte[](4); buf.append!int(5); writeln(buf); } --- So, what append is doing is writing into the buffer, which must have enough space to put the int. So, thus, it must have 4 bytes. Guess what happens if we try to store a long in it? Yeah, it'll break on that as well. You'll also notice that subsequent calls to append on that buf will overwrite what's already in there. It's not obvious, but append needs either an array with enough space to store the element or an output range, like appender. Maybe the documentation could use a little work in this regard.
Mar 28 2013
prev sibling next sibling parent reply "Chris Cain" <clcain uncg.edu> writes:
On Thursday, 28 March 2013 at 20:24:50 UTC, Vidar Wahlberg wrote:
 A bit on the side: It seems to me as importing std.bitmanip 
 somehow adds new properties to my array (".read()" and 
 ".write()", for example). Not necessarily a bad thing, more of 
 "I've not seen this before, I was expecting that I were to 
 concatenate the bytes from the conversion to my buffer using ~".
Sorry about the repeated postings ... I'm trying to read & answer it while also dealing with the norovirus :x --- import std.stdio : writeln; void main() { ubyte[] array; array ~= 5.toUbytes(); array ~= 6.toUbytes(); writeln(array); } ubyte[T.sizeof] toUbytes(T)(T val) safe { ubyte[T.sizeof] buf; std.bitmanip.append!T(buf[], val); return buf; } --- I see your latest post and see that you don't necessary care how to do it, but, I figured I might as well provide yet another way that's safe and simple. On Thursday, 28 March 2013 at 20:24:50 UTC, Vidar Wahlberg wrote:
 Great, I have a solution, so I go back to my project and 
 implement it like I implemented it in my test code, but when I 
 compile my project after this addition I get a new cryptic 
 error message: "Error: __overloadset isn't a template".
 After digging a bit I realized that it's because in my project 
 I also import std.file, apparently there are some collisions 
 between std.bitmanip and std.file. Again it's solvable, but 
 it's yet another fight with the language/standard library. I 
 would also assume that it's not that uncommon for a module that 
 use std.bitmanip to also use std.file, meaning that this error 
 potentially may occur often.
Yeah, that's a bit of an issue. Weird cryptic error. Oh well, as a general rule, try to import only the parts of the module you're actually using. This does two great things: it prevents namespace pollution causing errors like you've seen, and it documents WHERE someone has to look in order to find documentation on a function you're using in your module. As 1100110 noted, using a fully qualified ID is also a potential solution, especially if you only intend on using it in one place and the line isn't very noisy to begin with (as I showed in my example above).
 "why is this done like this, couldn't it be done much easier?".
Maybe. What needs to be made easier and how would you suggest to fix it? The error message, certainly. Probably the documentation too. But the API itself seems sane to me in this instance, it just needs a better description.
Mar 28 2013
next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Thursday, 28 March 2013 at 22:22:49 UTC, Chris Cain wrote:
 ---
 ubyte[T.sizeof] toUbytes(T)(T val)  safe {
     ubyte[T.sizeof] buf;
     std.bitmanip.append!T(buf[], val);
     return buf;
 }
 ---
This should be: ubyte[T.sizeof] toUbytes(T)(T val) safe { ubyte[T.sizeof] buf; import std.bitmanip : append; append!T(buf[], val); return buf; }
Mar 28 2013
prev sibling parent reply "Vidar Wahlberg" <vidar.wahlberg gmail.com> writes:
On Thursday, 28 March 2013 at 22:22:49 UTC, Chris Cain wrote:
 Sorry about the repeated postings ... I'm trying to read & 
 answer it while also dealing with the norovirus :x
Been there. Not amusing, I wish you well.
 "why is this done like this, couldn't it be done much easier?".
Maybe. What needs to be made easier and how would you suggest to fix it? The error message, certainly. Probably the documentation too. But the API itself seems sane to me in this instance, it just needs a better description.
Well, I'm not so proficient in the language yet that I'm going to climb to the top of Mount Stupid and say how it should be, because for what I know this may be perfectly logical with just me being blind to it, but to try to explain how it would make more sense to me: Since you got "ubyte[] buffer = [0, 0, 0, 0]; buffer.write!int(42); buffer.read!int();", I think it would be logical that "ubyte[] buffer; buffer.append!int(42); buffer.read!int()" would do pretty much the same (except instead of writing 4 bytes at index 0, it appends 4 bytes to the end of the array, then reads back the value). The latter code does however give you "Attempting to fetch the front of an empty array of ubyte". I don't get why you need to drag in std.array.appender() for std.bitmanip.append(), when you don't need it for read() and write().
Mar 28 2013
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, March 29, 2013 00:38:25 Vidar Wahlberg wrote:
 I don't get why you need to drag in std.array.appender() for
 std.bitmanip.append(), when you don't need it for read() and
 write().
Because read and write operate in place, and append doesn't. read and write operate on input ranges - read just reads the values from it as it iterates, and write overwrites what's there as it iterates. However, append uses an output range, because it's appending rather than overwriting, and for reasons that I don't understand, when treating an array as an output range, rather than appending (like an output range normally would), the put function (which is how stuff gets written to an output range) overwrites what's in the array rather than appending to it. So, using an empty array with any function operating on output ranges isn't going to work. Maybe there's a good reason for arrays working that way when they're treated as output ranges, but for me, it's a good reason to not use arrays when you need an output range. In either case, I'd suggest reading this if you want to know more about ranges: http://ddili.org/ders/d.en/ranges.html And since D's standard library ranges quite heavily, you're going to need at least a basic understanding of them if you want to use much of it. We really need a good article on ranges on dlang.org, but until we do, that link is probably your best resource for learning about ranges. - Jonathan M Davis
Mar 28 2013
parent reply "Vidar Wahlberg" <vidar.wahlberg gmail.com> writes:
On Friday, 29 March 2013 at 01:13:36 UTC, Jonathan M Davis wrote:
 In either case, I'd suggest reading this if you want to know 
 more about
 ranges:

 http://ddili.org/ders/d.en/ranges.html
Thank you, I will read that (when the time is not 0400). I feel I need to stress that this is something that quite possibly will scare away newcomers, that static arrays, dynamic arrays and ranges looks very similar to each other, but behaves differently. This is not the first time I fall in this pit, and I suspect it's not the last time either. And well, sorry for continuing to nag about this, but take a look at the documentation for read(), write() and append() in std.bitmanip: T read(T, En­dian en­di­an­ness = Endian.​bigEndian, R)(ref R range); void write(T, En­dian en­di­an­ness = Endian.​bigEndian, R)(R range, T value, size_t index); void ap­pend(T, En­dian en­di­an­ness = Endian.​bigEndian, R)(R range, T value); append() and write() are practically identical, just with write() having an extra parameter. The documentation even comes with examples for write() that use "ubyte[] buffer; buffer.write!ubyte(42, 0);", is it really odd that I assumed I could use append() in a similar matter when its parameters are exactly the same as for write() (minus the index)? Or is it unthinkable that I mistook arrays and Ranges for being interchangeable when the examples pass an array to a function that takes a Range? Ranges is something that's going to be new for a lot of people entering this language. When you know how arrays and ranges works in D I'm sure this makes perfect sense, but until you learn that, this is something that likely will confuse many people. Hopefully the article about ranges will clear things up for me.
Mar 28 2013
parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, March 29, 2013 04:01:40 Vidar Wahlberg wrote:
 Or is it
 unthinkable that I mistook arrays and Ranges for being
 interchangeable when the examples pass an array to a function
 that takes a Range?
I don't think that it's unthinkable at all (and arrays _are_ ranges; it's just that in the case of output ranges, arrays act a bit oddly). The documentation should probably be improved to make such a mistake less likely.
 Ranges is something that's going to be new for a lot of people
 entering this language. When you know how arrays and ranges works
 in D I'm sure this makes perfect sense, but until you learn that,
 this is something that likely will confuse many people.
 
 Hopefully the article about ranges will clear things up for me.
Yes. Ranges are incredibly powerful, but while the concept doesn't originate with D, AFAIK, actually using them in a serious API (particularly in a standard library) is unique to D. And our lack of good tutorials on them is probably our biggest documentation problem. It definitely needs to be fixed. - Jonathan M Davis
Mar 28 2013
prev sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
Definitely need to add some updates to the docs. Long story:

D provides an iterable interface called a Range. There are two 
base forms, inputRange and outputRange.

Dynamic Arrays have the privilege of being, a inputRange, 
outputRange, and a container.

An array however doesn't operate as you might expect, especially 
when using a function called append on it.

An output range consists of the ability call put for Range and 
Element (defined in std.range) for a dynamic array this means you 
can assign to front.

ubyte[] buffer;
buffer.append!ubyte(42);

The append function takes an outputRange, if we drill down the 
call that would be made (ignoring my value isn't correct)

buffer.front = 42;
buffer.popFront();

Thus when using an array as an outputRange it
1) Must have a size (hence the error: "Attempting to fetch the 
front of an empty array of ubyte")
2) Starts at the beginning (hence the observation: "instead of 
appending it behaves like write()")
3) Is consumed (You didn't run into this)

That is why arrays are awkward and the example makes use of 
appender (a more traditional form of an outputRange)

------------

The write function seems a little odd as it uses random access 
(indexing).

Instead of assigning to front like append does it assigns at 
index buffer[0]...

------------

The implementation of append is what you will find more in 
idiomatic D. In fact if the module was written today write 
wouldn't exist and append would probably have been named write.

-------------
-------------

"Error: __overloadset isn't a template"

That needs fixed, it usually does a better job of specifying 
conflicting modules across modules.

------------
------------

"It seems to me as importing std.bitmanip somehow adds new 
properties"

D provides UFCS (Uniform Function Call Syntax). For a given type 
A, foo(A a) is callable in both foo(a) and a.foo().

(Historical note: UFCS is recent addition, a bug allowed it to 
work with dynamic arrays like you see in these docs)
Mar 28 2013
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, March 29, 2013 03:11:08 Jesse Phillips wrote:
 The write function seems a little odd as it uses random access
 (indexing).
 
 Instead of assigning to front like append does it assigns at
 index buffer[0]...
That's because it's operating on multiple bytes at a time (e.g. writing the 4 bytes of an int at once). Really, it's  written for arrays and was generalized because it could be rather than really having been written for ranges.
 The implementation of append is what you will find more in
 idiomatic D. In fact if the module was written today write
 wouldn't exist and append would probably have been named write.
That could be argued for, but write and append do different things and both exist for a reason. Only having append would actually be problematic, as there are cases where you really do need write and not append. And neither of them have been in Phobos for all that long (peek, read, write, and append were added in 2.060). - Jonathan M Davis
Mar 28 2013
parent reply "Jesse Phillips" <Jessekphillips+D gmail.com> writes:
On Friday, 29 March 2013 at 02:39:59 UTC, Jonathan M Davis wrote:

 Only having append would actually be problematic, as there
 are cases where you really do need write and not append. And 
 neither of them
 have been in Phobos for all that long (peek, read, write, and 
 append were
 added in 2.060).

 - Jonathan M Davis
Ah, I was just going by the docs when I wrote this. I apparently got it wrong. write doesn't using indexing it uses opSliceAssign (which isn't in its constraints). I'm also not familiar with the use cases and there are definitely reasons to stray from idiomatic D.
Mar 29 2013
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, March 29, 2013 16:33:33 Jesse Phillips wrote:
 Ah, I was just going by the docs when I wrote this. I apparently
 got it wrong. write doesn't using indexing it uses opSliceAssign
 (which isn't in its constraints).
I'll have to take a look at that. I think that it was pretty much assuming that all of the slicing stuff would work with hasSlicing, but that stuff doesn't check for opSliceAssign, so clearly the template constraints need some work. Thanks for pointing that out. - Jonathan M Davis
Mar 29 2013
prev sibling next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg wrote:
 I know I'm probably going to upset some people with this, 
 bashing their favourite child and all, but I wanted to let you 
 know the experience I've had with D so far, as a novice D coder 
 with a heavy Java & light C++ background.
 It's not that I dislike D, in fact there are tons of things I 
 love about it, it's pretty much exactly what I'm looking for in 
 a programming language at the moment. Yet, I encounter some 
 frustrating issues when coding, often leaving me with the 
 impression that I'm fighting the language more than the problem 
 I'm trying to solve.
 True, there are many things I don't know about D, compilers or 
 the inner workings of a computer, and some of the fights I have 
 with the language are likely started by myself because I'm 
 dragging along my bias from other languages, drawing 
 misconceptions on how the D language actually works.
 My intentions are not to insult, but shed some light on some of 
 the difficulties I've faced (and how I solved them), with the 
 hope that it will help others from facing the same difficulties.
That is fine don't worry. Knowing what people have trouble with when starting with D is very valuable IMO.
 Woes:
 -----
 - I find myself in a world of pain when I want to share data 
 more complex than the basic data types (int, char, byte, etc) 
 across threads. Seemingly the magic trick is to "cast(shared) 
 foo" (or "cast(immutable)") when passing objects/references to 
 another thread, then "cast(Foo)" back on the receiving end (as 
 most classes/structs in the standard library refuse to let you 
 call any methods when the object is shared). The examples in 
 the source and TDPL are fairly limited on the issue, it mostly 
 covers only those basic data types.
Well, that is a long standing issue. shared is poorly defined and inconsistently implemented. Sad, but true.
 - While the "auto"-keyword often is great, it can lead to 
 difficulties, especially when used as the return type of a 
 function, such as "auto foo() { return bar; }". Sometimes you 
 may wish to store the result of a function/method call as a 
 global variable/class member, but when the function/method 
 returns "auto" it's not apparent what the data type may be. 
 While you may be able to find out what "bar" is by digging in 
 the source code, it can still be difficult to find. One example 
 is to save the result of "std.regex.match()" as a member in a 
 class. For me the solution was to "import std.traits", create a 
 function "auto matchText(string text) { return match(text, 
 myRegex); }" and define the class member as 
 "ReturnType!matchText matchResult;" (do also note that function 
 & member must come in the right order for this to compile). 
 This was all but obvious to a novice D coder as myself, the 
 solution was suggested to me in the IRC channel.
Yes, I have to say that it is a pain sometime. Additionally, it have some rough edges you may want to know : - Function that never return are inferred void. I would have preferred typeof(null) as void lead to many static and repetitive code for nothing when doing metaprograming. - Type inference handle very poorly recursion. It should simply exclude the recursion when doing type inference as it won't change the return type. The error message can be very opaque. - In some cases, you have to add explicit casts when implicit would have been enough in theory (but the type inference mechanism is confused). Back to your issue, you may want to use typeof . Sometime, it is plain better as type can become complex when doing metaprogramming (and phobos is full of this). You can also use an alias in order to make things look nice. alias FooT = typeof(foo()); FooT bar; bar = foo(); // Happy ?
 Gotchas:
 --------
 - The lack of "rectangular" arrays created at runtime in D 
 ("int i = 5; int[i][i] foo;") can be quite confusing for 
 programmers with Java or C++ background. Even though there 
 exists alternatives 
 (http://denis-sh.github.com/phobos-additions/unstd.multidim
nsionalarray.html), 
 this design decision and how to get around it when you really 
 desire a "rectangular" array could be explained in more detail 
 at http://dlang.org/arrays.html.

 - Static array versus dynamic array was one of the first traps 
 I stepped on 
 (http://forum.dlang.org/thread/jnu1an$rjr$1 digitalmars.com). 
 Until Jonathan M. Davis explained it in detail 
 (http://d.puremagic.com/issues/show_bug.cgi?id=8026#c4), I 
 pretty much considered it as "magic" that 
 "randomShuffle(staticArray);" did not sort the array while 
 "randomShuffle(staticArray[]);" did (the first call now gives 
 you an compile error, though). That static arrays are value 
 types while dynamic arrays are reference types may not be 
 obvious for those with primarily Java background.
Yes, Java have no value types, only reference types. This is actually a performance issue for 2 reasons : - You chase pointers, so you have cache miss and this is very costly on modern CPUs. - This is very GC unfriendly. For instance, on a project at a previous work, we had an LRU cache into an application. We knew that most object should traverse the cache and get out very fast, when other will stick in it most of the time for reason that'd be too long to explain. It lead to difficult to solve performances issues as while going through the LRU, object were considered as old by the GC and treated differently. As a consequence, the application generated a lot of garbage considered as old by the GC (when the GC assume that most objects dies young). It required a lot of work to make that work, when using value type would have solved the whole this instantly.
 - When casting a value to an enum, there's no checking that the 
 value actually is a valid enum value. Don't think I ever found 
 a solution on how to check whether the value after casting is a 
 valid enum value, it hasn't been a pressing issue.
Very true. enum as they stand have some safety issue IMO.
 - Compiling where DMD can't find all modules cause a rather 
 cryptic error message. A solution is to make sure you specify 
 all source files when compiling.
dmd is really uneven with error messages. Some are purely awesome, and other are completely cryptic. A very "funny" one is when you use import instead of module (dmd complain about the module conflicting with itself).
 Wishlist:
 ---------
 - "void[T]" associative array (i.e. a "set") would be nice, can 
 be achieved with "byte[0][T]".
Don't get me started on AA !
 - "Foo foo = new Foo();" for global variables/class members. 
 Now you must "Foo foo; static this() { foo = new Foo(); }".
Yes, as it imply an heap allocation. It is an harder problem that it seems as all thoses object could reference themselves.
Mar 27 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 03/27/2013 05:04 PM, deadalnix wrote:
 On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg wrote:
 ...
 - While the "auto"-keyword often is great, it can lead to
 difficulties, especially when used as the return type of a function,
 such as "auto foo() { return bar; }". Sometimes you may wish to store
 the result of a function/method call as a global variable/class
 member, but when the function/method returns "auto" it's not apparent
 what the data type may be. While you may be able to find out what
 "bar" is by digging in the source code, it can still be difficult to
 find. One example is to save the result of "std.regex.match()" as a
 member in a class. For me the solution was to "import std.traits",
 create a function "auto matchText(string text) { return match(text,
 myRegex); }" and define the class member as "ReturnType!matchText
 matchResult;" (do also note that function & member must come in the
 right order for this to compile). This was all but obvious to a novice
 D coder as myself, the solution was suggested to me in the IRC channel.
Yes, I have to say that it is a pain sometime. Additionally, it have some rough edges you may want to know : - Function that never return are inferred void. I would have preferred typeof(null) as void lead to many static and repetitive code for nothing when doing metaprograming.
I strongly disagree. What would be an example of the problems you are apparently experiencing?
   - Type inference handle very poorly recursion. It should simply
 exclude the recursion when doing type inference as it won't change the
 return type. The error message can be very opaque.
The cases that are allowed would need to be specified more rigorously.
   - In some cases, you have to add explicit casts when implicit would
 have been enough in theory (but the type inference mechanism is confused).

 ...

 - "Foo foo = new Foo();" for global variables/class members. Now you
 must "Foo foo; static this() { foo = new Foo(); }".
Yes, as it imply an heap allocation. It is an harder problem that it seems as all thoses object could reference themselves.
Just serialize the CTFE object graph into the static data segment.
Mar 27 2013
next sibling parent reply "Brad Anderson" <eco gnuk.net> writes:
On Wednesday, 27 March 2013 at 17:23:01 UTC, Timon Gehr wrote:
 On 03/27/2013 05:04 PM, deadalnix wrote:
 On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg
 ...
 - "Foo foo = new Foo();" for global variables/class members. 
 Now you
 must "Foo foo; static this() { foo = new Foo(); }".
Yes, as it imply an heap allocation. It is an harder problem that it seems as all thoses object could reference themselves.
Just serialize the CTFE object graph into the static data segment.
I believe that's what this pull aims to do: https://github.com/D-Programming-Language/dmd/pull/1724
Mar 27 2013
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 03/27/2013 06:34 PM, Brad Anderson wrote:
 On Wednesday, 27 March 2013 at 17:23:01 UTC, Timon Gehr wrote:
 On 03/27/2013 05:04 PM, deadalnix wrote:
 On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg
 ...
 - "Foo foo = new Foo();" for global variables/class members. Now you
 must "Foo foo; static this() { foo = new Foo(); }".
Yes, as it imply an heap allocation. It is an harder problem that it seems as all thoses object could reference themselves.
Just serialize the CTFE object graph into the static data segment.
I believe that's what this pull aims to do: https://github.com/D-Programming-Language/dmd/pull/1724
Almost. It does not support TLS, which is a severe limitation.
Mar 27 2013
prev sibling next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 27 March 2013 at 17:23:01 UTC, Timon Gehr wrote:
 I strongly disagree. What would be an example of the problems 
 you are apparently experiencing?
T foo(alias fallback)() { // Do some processing return the result. If an error occurs use fallback mechanism. } Reasonable thing to do as fallback is to try another method workaround the error, throw, whatever. The problem is that the fallback type inference makes it painful to work with, especially if fallback is a template itself. For instance, in SDC, you can parse ambiguous things as follow : parseTypeOrExpression!((parsed) { static if(is(typeof(parsed) : Expression)) { // Do something } else { throw SomeException(); } })(tokenRange); This is bound to fail. When a function never return, it make no sens to force a type on it and the magic subtype typeof(null) should be used (as typeof(null) can cast to anything, it is valid to call the function in any condition).
Mar 27 2013
next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 18:46:09 +0100
"deadalnix" <deadalnix gmail.com> wrote:

 On Wednesday, 27 March 2013 at 17:23:01 UTC, Timon Gehr wrote:
 I strongly disagree. What would be an example of the problems 
 you are apparently experiencing?
T foo(alias fallback)() { // Do some processing return the result. If an error occurs use fallback mechanism. } Reasonable thing to do as fallback is to try another method workaround the error, throw, whatever. The problem is that the fallback type inference makes it painful to work with, especially if fallback is a template itself. For instance, in SDC, you can parse ambiguous things as follow : parseTypeOrExpression!((parsed) { static if(is(typeof(parsed) : Expression)) { // Do something } else { throw SomeException(); } })(tokenRange); This is bound to fail. When a function never return, it make no sens to force a type on it and the magic subtype typeof(null) should be used (as typeof(null) can cast to anything, it is valid to call the function in any condition).
A "does not return" return type would be nice for other things anyway. For example: void error(string s) // Convenience helper { throw new MyException("blah blah blah: "~s); } void serveFilesForever() { while(true) listenAndRespond(); } int doStuff() { if(blah) return 1; else if(blah2) { // Bullshit compile error: // "Not all paths return a value" error("poop"); // Must add dead code, keep compiler happy: //assert(0); } else { // Same bullshit serveFilesForever(); //assert(0); } } Compare to: no_return error() // Convenience helper { //if(foo) return; // Oops! But compiler catches error. throw new MyException("blah blah blah"); } no_return serveFilesForever() { // This might be harder for the compiler to check :( while(true) listenAndRespond(); } int doStuff() { if(blah) return 1; else if(blah2) error("poop"); // No bullshit, just works else serveFilesForever(); // Whee! }
Mar 27 2013
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 03/27/2013 06:46 PM, deadalnix wrote:
 On Wednesday, 27 March 2013 at 17:23:01 UTC, Timon Gehr wrote:
 I strongly disagree. What would be an example of the problems you are
 apparently experiencing?
T foo(alias fallback)() { // Do some processing return the result. If an error occurs use fallback mechanism. } Reasonable thing to do as fallback is to try another method workaround the error, throw, whatever. The problem is that the fallback type inference makes it painful to work with, especially if fallback is a template itself. For instance, in SDC, you can parse ambiguous things as follow : parseTypeOrExpression!((parsed) { static if(is(typeof(parsed) : Expression)) { // Do something } else { throw SomeException(); } })(tokenRange);
I see. What is needed is a way to specify that a function does never return. (eg. a bottom type)
 This is bound to fail. When a function never return, it make no sens to
 force a type on it and the magic subtype typeof(null) should be used (as
 typeof(null) can cast to anything, it is valid to call the function in
 any condition).
It cannot cast to everything.
Mar 27 2013
prev sibling next sibling parent reply "Vidar Wahlberg" <vidar.wahlberg gmail.com> writes:
I'm impressed and most grateful for the feedback, I've learned 
some new things today :)

 int i = 5;
 auto foo = new int[][](i,i);
Won't this create an array (with 5 elements) of arrays (with 5 elements), also called a "jagged array"? Where memory is not necessarily continuously allocated and looking up values adds another layer of indirection?
 It is much more simple actually, "typeof(match(string.init, 
 Regex.init)) variable;" and no extra functions or source 
 digging is needed.
Many of you pointed this out, thanks, this a better solution than what I had. I needed to write "Regex!char.init" and not just "Regex.init", but that's just a minor detail.
 Yea, I'd imagine there would be some value-type/reference-type
 confusion from a lot of newcomers just because D *has* both 
 value types
 and reference types. As opposed to, say, Java where (almost?) 
 everything
 is a reference type, or C++ where everything is a value type, 
 etc.
 
 Personally, I find it very much worthwhile to have both value 
 and
 reference types. But you're right it is something many people 
 will have
 to learn to get used to, and particularly so with arrays.
I find it quite nice that you have both value and reference types, and for the most part it's rather clear in D when you're dealing with a reference and when you're dealing with a value. It was just arrays that caught me off guard, and I think others with a similar background may do the same mistake, so my comment about this really just is "arrays may require more explanation aimed at Java developers" :)
 But D has an easy solution - just use RDMD instead:
 
 rdmd --build-only -I{include paths as usual} {other flags} 
 main.d
That's a good tip! Somehow I had the notion that rdmd was purely a tool for "scripting", as in dynamically parsing code (like Python, Perl, etc), so I never looked much into it.
Mar 27 2013
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 07:06:45PM +0100, Vidar Wahlberg wrote:
[...]
Yea, I'd imagine there would be some value-type/reference-type
confusion from a lot of newcomers just because D *has* both value
types and reference types. As opposed to, say, Java where (almost?)
everything is a reference type, or C++ where everything is a value
type, etc.

Personally, I find it very much worthwhile to have both value and
reference types. But you're right it is something many people will
have to learn to get used to, and particularly so with arrays.
I find it quite nice that you have both value and reference types, and for the most part it's rather clear in D when you're dealing with a reference and when you're dealing with a value. It was just arrays that caught me off guard, and I think others with a similar background may do the same mistake, so my comment about this really just is "arrays may require more explanation aimed at Java developers" :)
Yeah, the documentation needs to be improved. Maybe file an enhancement bug for this at d.puremagic.com/issues ?
But D has an easy solution - just use RDMD instead:

rdmd --build-only -I{include paths as usual} {other flags} main.d
That's a good tip! Somehow I had the notion that rdmd was purely a tool for "scripting", as in dynamically parsing code (like Python, Perl, etc), so I never looked much into it.
rdmd gives D a scripting-like interface, but D is inherently a compiled language, so it isn't actually a D interpreter. :) It's just that D compilation (esp. with DMD) is incredibly fast, given what it does, so that calling rdmd is almost like "interpreting" D code on-the-fly. What it actually does, of course, is to compile the code and cache the compiled objects, so running it multiple times does not repeatedly incur the compile-time overhead. T -- Caffeine underflow. Brain dumped.
Mar 27 2013
parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 11:13:31 -0700
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:

 On Wed, Mar 27, 2013 at 07:06:45PM +0100, Vidar Wahlberg wrote:
 [...]
 
 I find it quite nice that you have both value and reference types,
 and for the most part it's rather clear in D when you're dealing
 with a reference and when you're dealing with a value. It was just
 arrays that caught me off guard, and I think others with a similar
 background may do the same mistake, so my comment about this really
 just is "arrays may require more explanation aimed at Java
 developers" :)
Yea, the arrays definitely do blur the value/reference lines. Aside from static/dynamic, another example of this is how a dynamic array's *values* are reference, but it's length is by-value. Of course, it's very simple when you realize that a D dynamic array is more-or-less like this: struct Array(T) { size_t length; T* ptr; } But still, it's definitely something to get used to.
But D has an easy solution - just use RDMD instead:

rdmd --build-only -I{include paths as usual} {other flags} main.d
That's a good tip! Somehow I had the notion that rdmd was purely a tool for "scripting", as in dynamically parsing code (like Python, Perl, etc), so I never looked much into it.
It was originally designed for scripting uses. But making that work well required adding the feature of "*automatically* detect and compile all required sources". And that feature turned out to be very useful just for its own sake, so RDMD grew into something that could nicely handle both.
 
 rdmd gives D a scripting-like interface, but D is inherently a
 compiled language, so it isn't actually a D interpreter. :) 
Sure it is! It's an AOT interpreter! (Which is ironically something very well-respected and sought-after in interpreted-language circles. Go figure: they've reinvented native compilation and simply gave it a new name.)
Mar 27 2013
prev sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Wednesday, 27 March 2013 at 18:06:46 UTC, Vidar Wahlberg wrote:
 I'm impressed and most grateful for the feedback, I've learned 
 some new things today :)

 int i = 5;
 auto foo = new int[][](i,i);
Won't this create an array (with 5 elements) of arrays (with 5 elements), also called a "jagged array"? Where memory is not necessarily continuously allocated and looking up values adds another layer of indirection?
Unfortunately yes. However, it's not a hard problem to overcome with a wrapper struct over a contiguous array.
Mar 27 2013
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 07:41:42PM +0100, John Colvin wrote:
 On Wednesday, 27 March 2013 at 18:06:46 UTC, Vidar Wahlberg wrote:
I'm impressed and most grateful for the feedback, I've learned
some new things today :)

int i = 5;
auto foo = new int[][](i,i);
Won't this create an array (with 5 elements) of arrays (with 5 elements), also called a "jagged array"? Where memory is not necessarily continuously allocated and looking up values adds another layer of indirection?
Unfortunately yes. However, it's not a hard problem to overcome with a wrapper struct over a contiguous array.
Which is what Denis' multidimensional array implementation does. As does my implementation as well. This seems to be quite a common use-case; we should put this into Phobos IMO. T -- People tell me that I'm skeptical, but I don't believe it.
Mar 27 2013
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/27/13 3:18 PM, H. S. Teoh wrote:
 Which is what Denis' multidimensional array implementation does. As does
 my implementation as well.

 This seems to be quite a common use-case; we should put this into Phobos
 IMO.
Agree. Do you (or Denis) have something in reviewable form? Andrei
Mar 27 2013
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 04:28:16PM -0400, Andrei Alexandrescu wrote:
 On 3/27/13 3:18 PM, H. S. Teoh wrote:
Which is what Denis' multidimensional array implementation does. As does
my implementation as well.

This seems to be quite a common use-case; we should put this into Phobos
IMO.
Agree. Do you (or Denis) have something in reviewable form?
Here is Denis' implementation: https://github.com/denis-sh/phobos-additions/blob/master/unstd/multidimensionalarray.d As I didn't write the code, I can't say how review-ready it is. My own implementation is somewhat incomplete at this time, so it's not quite ready for review yet. It's missing some functionality that Denis' implementation has (arbitrary index reordering, more complete range-based access, opApply, etc.). T -- EMACS = Extremely Massive And Cumbersome System
Mar 27 2013
prev sibling parent Denis Shelomovskij <verylonglogin.reg gmail.com> writes:
28.03.2013 0:28, Andrei Alexandrescu пишет:
 On 3/27/13 3:18 PM, H. S. Teoh wrote:
 Which is what Denis' multidimensional array implementation does. As does
 my implementation as well.

 This seems to be quite a common use-case; we should put this into Phobos
 IMO.
Agree. Do you (or Denis) have something in reviewable form? Andrei
No. The module is 2 years old and I only made changes on dmd requests. But if such functionality is really needed I will be happy to revise API and prepare my implementation for review. P.S. It's an accident I have seen this post. I think in the case of such questions it is obligatory to e-mail the author or there is a big risk the question will bot be delivered. -- Денис В. Шеломовский Denis V. Shelomovskij
Oct 08 2013
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/27/13 1:23 PM, Timon Gehr wrote:
 - Function that never return are inferred void. I would have preferred
 typeof(null) as void lead to many static and repetitive code for nothing
 when doing metaprograming.
I strongly disagree.
Ideally such function should return a "none" type, the bottom of the hierarchy lattice. We don't have such, so returning typeof(null) (which we do have) is the next best choice as it's just above bottom. Andrei
Mar 27 2013
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 03/27/2013 07:20 PM, Andrei Alexandrescu wrote:
 On 3/27/13 1:23 PM, Timon Gehr wrote:
 - Function that never return are inferred void. I would have preferred
 typeof(null) as void lead to many static and repetitive code for nothing
 when doing metaprograming.
I strongly disagree.
Ideally such function should return a "none" type, the bottom of the hierarchy lattice. We don't have such, so returning typeof(null) (which we do have) is the next best choice as it's just above bottom. Andrei
Maybe it is one next best choice. It is still a horrible choice. It's not any more just above bottom than the following type: struct JustAboveBottom{ } We should either go with the real thing or do nothing about it.
Mar 27 2013
prev sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 27 March 2013 at 18:20:49 UTC, Andrei Alexandrescu 
wrote:
 On 3/27/13 1:23 PM, Timon Gehr wrote:
 - Function that never return are inferred void. I would have 
 preferred
 typeof(null) as void lead to many static and repetitive code 
 for nothing
 when doing metaprograming.
I strongly disagree.
Ideally such function should return a "none" type, the bottom of the hierarchy lattice. We don't have such, so returning typeof(null) (which we do have) is the next best choice as it's just above bottom.
I thought that typeof(null) was that bottom type. What is the difference ? Anyway, void isn't the right choice here and is a pain to work with.
Mar 27 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 03/28/2013 04:18 AM, deadalnix wrote:
 On Wednesday, 27 March 2013 at 18:20:49 UTC, Andrei Alexandrescu wrote:
 On 3/27/13 1:23 PM, Timon Gehr wrote:
 - Function that never return are inferred void. I would have preferred
 typeof(null) as void lead to many static and repetitive code for
 nothing
 when doing metaprograming.
I strongly disagree.
Ideally such function should return a "none" type, the bottom of the hierarchy lattice. We don't have such, so returning typeof(null) (which we do have) is the next best choice as it's just above bottom.
I thought that typeof(null) was that bottom type. What is the difference ?
There is a huge difference. - typeof(null) is a subtype of all _class, interface and pointer_ types because they all _include_ its value, null. - bottom is a subtype of _all_ types, because there is _no_ value of type bottom.
 Anyway, void isn't the right choice here and is a pain to work with.
typeof(null) would be worse.
Mar 28 2013
parent "deadalnix" <deadalnix gmail.com> writes:
On Thursday, 28 March 2013 at 10:34:35 UTC, Timon Gehr wrote:
 On 03/28/2013 04:18 AM, deadalnix wrote:
 On Wednesday, 27 March 2013 at 18:20:49 UTC, Andrei 
 Alexandrescu wrote:
 On 3/27/13 1:23 PM, Timon Gehr wrote:
 - Function that never return are inferred void. I would 
 have preferred
 typeof(null) as void lead to many static and repetitive 
 code for
 nothing
 when doing metaprograming.
I strongly disagree.
Ideally such function should return a "none" type, the bottom of the hierarchy lattice. We don't have such, so returning typeof(null) (which we do have) is the next best choice as it's just above bottom.
I thought that typeof(null) was that bottom type. What is the difference ?
There is a huge difference. - typeof(null) is a subtype of all _class, interface and pointer_ types because they all _include_ its value, null. - bottom is a subtype of _all_ types, because there is _no_ value of type bottom.
OK I see the difference.
 Anyway, void isn't the right choice here and is a pain to work 
 with.
typeof(null) would be worse.
I don't see how it is worse.
Mar 28 2013
prev sibling next sibling parent "Dicebot" <m.strashun gmail.com> writes:
Welcome and thanks for sharing your experience! Few (hopefully) 
useful hints:

 -----
 - I find myself in a world of pain when I want to share data 
 more complex than the basic data types (int, char, byte, etc) 
 across threads. Seemingly the magic trick is to "cast(shared) 
 foo" (or "cast(immutable)") when passing objects/references to 
 another thread, then "cast(Foo)" back on the receiving end (as 
 most classes/structs in the standard library refuse to let you 
 call any methods when the object is shared). The examples in 
 the source and TDPL are fairly limited on the issue, it mostly 
 covers only those basic data types.
That is somewhat intended as D proposes message-passing a "default" multi-threading approach and makes sharing global data intentionally difficult. What lacks though, is some solid article series about how "D way" of doing things here, it is often quite not obvious. Definitely an area for improvement.
 - While the "auto"-keyword often is great, it can lead to 
 difficulties, especially when used as the return type of a 
 function, such as "auto foo() { return bar; }". Sometimes you 
 may wish to store the result of a function/method call as a 
 global variable/class member, but when the function/method 
 returns "auto" it's not apparent what the data type may be. 
 While you may be able to find out what "bar" is by digging in 
 the source code, it can still be difficult to find. One example 
 is to save the result of "std.regex.match()" as a member in a 
 class. For me the solution was to "import std.traits", create a 
 function "auto matchText(string text) { return match(text, 
 myRegex); }" and define the class member as 
 "ReturnType!matchText matchResult;" (do also note that function 
 & member must come in the right order for this to compile). 
 This was all but obvious to a novice D coder as myself, the 
 solution was suggested to me in the IRC channel.
It is much more simple actually, "typeof(match(string.init, Regex.init)) variable;" and no extra functions or source digging is needed. D static introspection is so much more powerful than in other languages that is often completely overlooked by newcomers.
 - Static array versus dynamic array was one of the first traps 
 I stepped on 
 (http://forum.dlang.org/thread/jnu1an$rjr$1 digitalmars.com). 
 Until Jonathan M. Davis explained it in detail 
 (http://d.puremagic.com/issues/show_bug.cgi?id=8026#c4), I 
 pretty much considered it as "magic" that 
 "randomShuffle(staticArray);" did not sort the array while 
 "randomShuffle(staticArray[]);" did (the first call now gives 
 you an compile error, though). That static arrays are value 
 types while dynamic arrays are reference types may not be 
 obvious for those with primarily Java background.
Unfortunately, there is a conflict of interest when targeting both programmers with Java and C background at the same time, what D tries to do. Sometimes it is very hard to resolve right.
 - When casting a value to an enum, there's no checking that the 
 value actually is a valid enum value. Don't think I ever found 
 a solution on how to check whether the value after casting is a 
 valid enum value, it hasn't been a pressing issue.
You most likely want std.conv.to - Phobos lexical cast. It does check enum valid values.
 - Compiling where DMD can't find all modules cause a rather 
 cryptic error message. A solution is to make sure you specify 
 all source files when compiling.
Have you tried rdmd?
Mar 27 2013
prev sibling next sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg wrote:

 - While the "auto"-keyword often is great, it can lead to 
 difficulties, especially when used as the return type of a 
 function, such as "auto foo() { return bar; }". Sometimes you 
 may wish to store the result of a function/method call as a 
 global variable/class member, but when the function/method 
 returns "auto" it's not apparent what the data type may be. 
 While you may be able to find out what "bar" is by digging in 
 the source code, it can still be difficult to find. One example 
 is to save the result of "std.regex.match()" as a member in a 
 class. For me the solution was to "import std.traits", create a 
 function "auto matchText(string text) { return match(text, 
 myRegex); }" and define the class member as 
 "ReturnType!matchText matchResult;" (do also note that function 
 & member must come in the right order for this to compile). 
 This was all but obvious to a novice D coder as myself, the 
 solution was suggested to me in the IRC channel.
This is actually nothing to do with auto. It's endemic to all templated returns from functions and the same is true in c++ (I don't know enough about java generics to comment). A solution to your particular problem class A(type_of_myRegex) { alias ReturnType!(match!(string, type_of_myRegex)) RegexR; RegexR r; void foo(string text, type_of_myRegex myRegex) { r = match(text, myRegex); } } of course, if you only ever use one Regex type then of course you could do without the templating in the class and hard-code it in.
Mar 27 2013
parent "John Colvin" <john.loughran.colvin gmail.com> writes:
On Wednesday, 27 March 2013 at 16:15:45 UTC, John Colvin wrote:

 This is actually nothing to do with auto. It's endemic to all 
 templated returns from functions and the same is true in c++ (I 
 don't know enough about java generics to comment).
Sorry, I'd want to correct this to "This is only partly to do with auto".
Mar 27 2013
prev sibling next sibling parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 16:34:19 +0100
"Vidar Wahlberg" <vidar.wahlberg gmail.com> wrote:
=20
 Woes:
 -----
 - I find myself in a world of pain when I want to share data more=20
 complex than the basic data types (int, char, byte, etc) across=20
 threads. Seemingly the magic trick is to "cast(shared) foo" (or=20
 "cast(immutable)") when passing objects/references to another=20
 thread, then "cast(Foo)" back on the receiving end (as most=20
 classes/structs in the standard library refuse to let you call=20
 any methods when the object is shared). The examples in the=20
 source and TDPL are fairly limited on the issue, it mostly covers=20
 only those basic data types.
=20
I haven't been dealing with threads or "shared", but my understanding is that casting to and from shared is not recommended as it (deliberately) subverts the safety checks in the type system. Although can't really say what the right way to handle it is since, as I said, I've never dealt with that part of the language.
 - While the "auto"-keyword often is great, it can lead to=20
 difficulties, especially when used as the return type of a=20
 function, such as "auto foo() { return bar; }". Sometimes you may=20
 wish to store the result of a function/method call as a global=20
 variable/class member, but when the function/method returns=20
 "auto" it's not apparent what the data type may be. While you may=20
 be able to find out what "bar" is by digging in the source code,=20
Here's the trick I like to use: // Some var with unknown type auto var =3D ...; pragma(msg, "var: " ~ typeof(var).stringof); That will print something like this at compile-time: var: int Unfortunately (or fortunately, depending on your point of view), it ignores all convenience aliases and will always give you the *full* original concrete static type, instead of any prettied-up aliases for it, but it does work and gets the job done.
=20
 - Static array versus dynamic array was one of the first traps I=20
 stepped on=20
 (http://forum.dlang.org/thread/jnu1an$rjr$1 digitalmars.com).=20
 Until Jonathan M. Davis explained it in detail=20
 (http://d.puremagic.com/issues/show_bug.cgi?id=3D8026#c4), I pretty=20
 much considered it as "magic" that "randomShuffle(staticArray);"=20
 did not sort the array while "randomShuffle(staticArray[]);" did=20
 (the first call now gives you an compile error, though). That=20
 static arrays are value types while dynamic arrays are reference=20
 types may not be obvious for those with primarily Java background.
=20
Yea, I'd imagine there would be some value-type/reference-type confusion from a lot of newcomers just because D *has* both value types and reference types. As opposed to, say, Java where (almost?) everything is a reference type, or C++ where everything is a value type, etc. Personally, I find it very much worthwhile to have both value and reference types. But you're right it is something many people will have to learn to get used to, and particularly so with arrays.
 - When casting a value to an enum, there's no checking that the=20
 value actually is a valid enum value. Don't think I ever found a=20
 solution on how to check whether the value after casting is a=20
 valid enum value, it hasn't been a pressing issue.
=20
Honestly, I hate that, too. The problem is that enum is (unfortunately) intended to do double-duty as a bitfield so you can do something like this: enum Options { FeatureA =3D 0b0000_0001; FeatureB =3D 0b0000_0010; FeatureC =3D 0b0000_0100; FeatureD =3D 0b0000_1000; // etc... } // Use features A and C auto myOptions =3D Options.FeatureA | Options.FeatureC; That possibility means that D *can't* check for validity as you suggest. I'm convinced that scenario *should* be considered an entirely separate thing because cramming it together with regular enumerations creates conflicting goals with the two usages of "enum", and forces unfortunate design compromises with both.
 - Compiling where DMD can't find all modules cause a rather=20
 cryptic error message. A solution is to make sure you specify all=20
 source files when compiling.
=20
D uses the C/C++ compilation model: It doesn't compile any files you don't specifically tell it to compile. Remember, importing is not the same as compiling: Merely importing a file only makes the symbols visible, it doesn't automatically *compile* the imported file. You have to tell it to do that. Non-C/C++ developers often get tripped up on this, but it's normal and expected for C/C++ toolchains or really anything that uses a separate "link" step. But D has an easy solution - just use RDMD instead: rdmd --build-only -I{include paths as usual} {other flags} main.d Just give it your *main* D file (*must* be the *last* argument), and it'll automatically find all .d files needed and pass them all to DMD.
=20
 Wishlist:
 ---------
 - "void[T]" associative array (i.e. a "set") would be nice, can=20
 be achieved with "byte[0][T]".
=20
I agree. I've just been using "bool[T]", but "byte[0][T]" is a good idea. Could probably do this, too, to help out: template HashSet(T) { alias byte[0][T] HashSet; } Then just "HashSet!int", "HashSet!string", "HashSet!Foo", etc.
 - "Foo foo =3D new Foo();" for global variables/class members. Now=20
 you must "Foo foo; static this() {=A0foo =3D new Foo();=A0}".
IMO, it's better to do lazy initialization: private Foo _foo; private bool fooInited; property ref Foo foo() { if(!_foo && !fooInited) { _foo =3D new Foo(); fooInited =3D true; } return _foo; } I prefer to avoid "static this()" because that makes it easy to end up with a "cyclic dependency" error on program startup. If two modules that import each other (directly or indirectly) both have a "static this()", then the runtime has no way to know which needs to run first, so it gives a cyclic dependency error instead. This lazy-initialization avoids that problem. Of course, it certainly doesn't help with the convenience issue. But all that boilerplate could all be wrapped up in a reusable convenience mixin. I think that would be a great thing to have in Phobos.
Mar 27 2013
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 12:52:25PM -0400, Nick Sabalausky wrote:
 On Wed, 27 Mar 2013 16:34:19 +0100
 "Vidar Wahlberg" <vidar.wahlberg gmail.com> wrote:
[...]
 - While the "auto"-keyword often is great, it can lead to 
 difficulties, especially when used as the return type of a 
 function, such as "auto foo() { return bar; }". Sometimes you may 
 wish to store the result of a function/method call as a global 
 variable/class member, but when the function/method returns 
 "auto" it's not apparent what the data type may be. While you may 
 be able to find out what "bar" is by digging in the source code, 
Here's the trick I like to use: // Some var with unknown type auto var = ...; pragma(msg, "var: " ~ typeof(var).stringof); That will print something like this at compile-time: var: int Unfortunately (or fortunately, depending on your point of view), it ignores all convenience aliases and will always give you the *full* original concrete static type, instead of any prettied-up aliases for it, but it does work and gets the job done.
What's wrong with using typeof? auto var = ...; typeof(var) anotherVar; ... anotherVar = var; [...]
 - When casting a value to an enum, there's no checking that the 
 value actually is a valid enum value. Don't think I ever found a 
 solution on how to check whether the value after casting is a 
 valid enum value, it hasn't been a pressing issue.
 
Honestly, I hate that, too. The problem is that enum is (unfortunately) intended to do double-duty as a bitfield so you can do something like this: enum Options { FeatureA = 0b0000_0001; FeatureB = 0b0000_0010; FeatureC = 0b0000_0100; FeatureD = 0b0000_1000; // etc... } // Use features A and C auto myOptions = Options.FeatureA | Options.FeatureC; That possibility means that D *can't* check for validity as you suggest.
As a compromise for now, I'd just use std.conv.to for when I want enum values checked. In any case, using casts should be avoided in D unless there's no other way around it. Casting is a system-level operation, and most application code shouldn't be using it.
 I'm convinced that scenario *should* be considered an entirely separate
 thing because cramming it together with regular enumerations creates
 conflicting goals with the two usages of "enum", and forces unfortunate
 design compromises with both.
[...] Yeah, I find using enums for bitfields a bit ugly. I mean, I love using bitfields too (don't know if this is because of my C/C++ background tending toward premature optimization) but conflating them with enums that supposedly should have unique values is IMO a language smell. (And don't get me started on enums being manifest constants instead of "real" enums... I still find that jarring.) T -- Marketing: the art of convincing people to pay for what they didn't need before which you can't deliver after.
Mar 27 2013
prev sibling next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 27 March 2013 at 16:52:27 UTC, Nick Sabalausky 
wrote:
 Honestly, I hate that, too. The problem is that enum is 
 (unfortunately)
 intended to do double-duty as a bitfield so you can do 
 something like
 this:

 enum Options
 {
     FeatureA = 0b0000_0001;
     FeatureB = 0b0000_0010;
     FeatureC = 0b0000_0100;
     FeatureD = 0b0000_1000;
     // etc...
 }

 // Use features A and C
 auto myOptions = Options.FeatureA | Options.FeatureC;

 That possibility means that D *can't* check for validity as you 
 suggest.
It can. myOptions is an int here, as Options would decay to its base type.
Mar 27 2013
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 27 Mar 2013 13:32:06 -0400, deadalnix <deadalnix gmail.com> wrote:

 On Wednesday, 27 March 2013 at 16:52:27 UTC, Nick Sabalausky wrote:
 Honestly, I hate that, too. The problem is that enum is (unfortunately)
 intended to do double-duty as a bitfield so you can do something like
 this:

 enum Options
 {
     FeatureA = 0b0000_0001;
     FeatureB = 0b0000_0010;
     FeatureC = 0b0000_0100;
     FeatureD = 0b0000_1000;
     // etc...
 }

 // Use features A and C
 auto myOptions = Options.FeatureA | Options.FeatureC;

 That possibility means that D *can't* check for validity as you suggest.
It can. myOptions is an int here, as Options would decay to its base type.
No, it's not. try it. I thought as you did too until recently. And before you go checking, it's not a bug, it's functioning per the spec. -Steve
Mar 27 2013
parent "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 27 March 2013 at 17:36:46 UTC, Steven Schveighoffer 
wrote:
 On Wed, 27 Mar 2013 13:32:06 -0400, deadalnix 
 <deadalnix gmail.com> wrote:

 On Wednesday, 27 March 2013 at 16:52:27 UTC, Nick Sabalausky 
 wrote:
 Honestly, I hate that, too. The problem is that enum is 
 (unfortunately)
 intended to do double-duty as a bitfield so you can do 
 something like
 this:

 enum Options
 {
    FeatureA = 0b0000_0001;
    FeatureB = 0b0000_0010;
    FeatureC = 0b0000_0100;
    FeatureD = 0b0000_1000;
    // etc...
 }

 // Use features A and C
 auto myOptions = Options.FeatureA | Options.FeatureC;

 That possibility means that D *can't* check for validity as 
 you suggest.
It can. myOptions is an int here, as Options would decay to its base type.
No, it's not. try it. I thought as you did too until recently.
I knew the bug existed, but I thought it would be solved now. If it is how the spec specify it, then it is a spec bug and a compiler bug.
Mar 27 2013
prev sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 18:32:06 +0100
"deadalnix" <deadalnix gmail.com> wrote:

 On Wednesday, 27 March 2013 at 16:52:27 UTC, Nick Sabalausky 
 wrote:
 Honestly, I hate that, too. The problem is that enum is 
 (unfortunately)
 intended to do double-duty as a bitfield so you can do 
 something like
 this:

 enum Options
 {
     FeatureA = 0b0000_0001;
     FeatureB = 0b0000_0010;
     FeatureC = 0b0000_0100;
     FeatureD = 0b0000_1000;
     // etc...
 }

 // Use features A and C
 auto myOptions = Options.FeatureA | Options.FeatureC;

 That possibility means that D *can't* check for validity as you 
 suggest.
It can. myOptions is an int here, as Options would decay to its base type.
Options myOptions = Options.FeatureA | Options.FeatureC;
Mar 27 2013
prev sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Nick Sabalausky:

 enum Options
 {
     FeatureA = 0b0000_0001;
     FeatureB = 0b0000_0010;
     FeatureC = 0b0000_0100;
     FeatureD = 0b0000_1000;
     // etc...
 }

 // Use features A and C
 auto myOptions = Options.FeatureA | Options.FeatureC;

 That possibility means that D *can't* check for validity as you 
 suggest.

 I'm convinced that scenario *should* be considered an entirely 
 separate
 thing because cramming it together with regular enumerations 
 creates
 conflicting goals with the two usages of "enum", and forces 
 unfortunate
 design compromises with both.
This was discussed in past. A library code BitFlags similar to struct bitfields is probably able to solve most of this problem in a mostly type safe way. If you want a built-in solution, with a bitflags, you will have to wait longer. Bye, bearophile
Mar 27 2013
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 04:34:19PM +0100, Vidar Wahlberg wrote:
[...]
 - While the "auto"-keyword often is great, it can lead to
 difficulties, especially when used as the return type of a function,
 such as "auto foo() { return bar; }". Sometimes you may wish to store
 the result of a function/method call as a global variable/class
 member, but when the function/method returns "auto" it's not apparent
 what the data type may be.
This is true. Sometimes you really *do* want to know what the return type of an auto function is. However, you can work around this by using typeof: auto func() { return mysteriousType(); } void main() { alias RetType = typeof(func()); // Look, ma! I can use a type without knowing what it // is! RetType t = func(); ... }
 While you may be able to find out what "bar" is by digging in the
 source code, it can still be difficult to find.
I think this is a bad solution. A library user shouldn't need to look at library source code to figure out what a return type is.
 One example is to save the result of "std.regex.match()" as a member
 in a class. For me the solution was to "import std.traits", create a
 function "auto matchText(string text) { return match(text, myRegex);
 }" and define the class member as "ReturnType!matchText matchResult;"
 (do also note that function & member must come in the right order for
 this to compile). This was all but obvious to a novice D coder as
 myself, the solution was suggested to me in the IRC channel.
I find this solution a bit cumbersome. D already has a built-in typeof operator, there's no need to write tons of wrappers everywhere just to get a return type out. I would write it like this: class C { // Capture the return type of std.regex.match alias MatchResult = typeof(std.regex.match("", "")); // Now you can store it MatchResult result; this(string target, string regex) { result = std.regex.match(target, regex); } }
 Gotchas:
 --------
 - The lack of "rectangular" arrays created at runtime in D ("int i =
 5; int[i][i] foo;") can be quite confusing for programmers with Java
 or C++ background. Even though there exists alternatives
(http://denis-sh.github.com/phobos-additions/unstd.multidimensionalarray.html),
 this design decision and how to get around it when you really desire
 a "rectangular" array could be explained in more detail at
 http://dlang.org/arrays.html.
We really need rectangular arrays in Phobos. Denis has written one, as you linked above, and I've written one, too (which is similar to Denis' version). But this is such a common usage that we really should have a library module for it. Note, however, that if all but 1 of the dimensions are known, then you *can* have rectangular arrays: // This is a rectangular array int[3][4][5] rect; See also: http://wiki.dlang.org/Dense_multidimensional_arrays
 - Static array versus dynamic array was one of the first traps I
 stepped on
 (http://forum.dlang.org/thread/jnu1an$rjr$1 digitalmars.com). Until
 Jonathan M. Davis explained it in detail
 (http://d.puremagic.com/issues/show_bug.cgi?id=8026#c4), I pretty
 much considered it as "magic" that "randomShuffle(staticArray);" did
 not sort the array while "randomShuffle(staticArray[]);" did (the
 first call now gives you an compile error, though). That static
 arrays are value types while dynamic arrays are reference types may
 not be obvious for those with primarily Java background.
Well, the documentation needs to be improved in this regard, that's for sure. But language-wise, there's really nothing wrong with it (it's not a sin to be different from Java).
 - When casting a value to an enum, there's no checking that the value
 actually is a valid enum value. Don't think I ever found a solution on
 how to check whether the value after casting is a valid enum value, it
 hasn't been a pressing issue.
Yeah, I find this to be questionable behaviour too. But then again, in D, one is expected to use std.conv for conversions (casting is generally not recommended unless there's no other way to do it), and std.conv.conv does check for valid enum values: enum A { abc = 100, def = 200 } A a; writeln(a); // prints "abc" a = to!A(150); // this throws a runtime exception: 150 is not // in the enum.
 - Compiling where DMD can't find all modules cause a rather cryptic
 error message. A solution is to make sure you specify all source
 files when compiling.
You could use rdmd, which I believe automatically scans for all source files you depend on.
 Wishlist:
 ---------
 - "void[T]" associative array (i.e. a "set") would be nice, can be
 achieved with "byte[0][T]".
I think we should make a Phobos module for this. I, for one, found myself needing such a type.
 - "Foo foo = new Foo();" for global variables/class members. Now you
 must "Foo foo; static this() {foo = new Foo();}".
Yeah, this is an irksome limitation. Though, it does have the benefit of not letting you do something that incurs runtime-cost without being explicit about it. Still, it is cumbersome to have to use static this() all over the place. It would be nice for the compiler to automatically lower such things into implicit ctor calls. T -- A bend in the road is not the end of the road unless you fail to make the turn. -- Brian White
Mar 27 2013
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 10:05:08 -0700
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:

 On Wed, Mar 27, 2013 at 04:34:19PM +0100, Vidar Wahlberg wrote:
 [...]
=20
 - "Foo foo =3D new Foo();" for global variables/class members. Now you
 must "Foo foo; static this() {=A0foo =3D new Foo();=A0}".
=20 Yeah, this is an irksome limitation. Though, it does have the benefit of not letting you do something that incurs runtime-cost without being explicit about it. Still, it is cumbersome to have to use static this() all over the place. It would be nice for the compiler to automatically lower such things into implicit ctor calls. =20
I wouldn't want such things to be implicitly converted to static ctors unless the cyclic dependency issue was somehow ironed out. I don't mind initializers of mutable vars being implicitly run at runtime (that convenience would certainly be nice), but I definitely don't want "cyclic dependency error" being triggered by something so innocent-looking.
Mar 27 2013
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 01:59:13PM -0400, Nick Sabalausky wrote:
 On Wed, 27 Mar 2013 10:05:08 -0700
 "H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:
 
 On Wed, Mar 27, 2013 at 04:34:19PM +0100, Vidar Wahlberg wrote:
 [...]
 
 - "Foo foo = new Foo();" for global variables/class members. Now you
 must "Foo foo; static this() { foo = new Foo(); }".
Yeah, this is an irksome limitation. Though, it does have the benefit of not letting you do something that incurs runtime-cost without being explicit about it. Still, it is cumbersome to have to use static this() all over the place. It would be nice for the compiler to automatically lower such things into implicit ctor calls.
I wouldn't want such things to be implicitly converted to static ctors unless the cyclic dependency issue was somehow ironed out.
True.
 I don't mind initializers of mutable vars being implicitly run at
 runtime (that convenience would certainly be nice), but I definitely
 don't want "cyclic dependency error" being triggered by something so
 innocent-looking.
I wonder if it would solve the problem if different instances of static ctors in the same module are regarded as separate entities with their own dependencies, so that you won't get a cyclic dependency error unless there's truly an irreducible cyclic dependency. That is, cyclic dependency errors shouldn't happen just because *one* static ctor depends on stuff that indirectly depends on one of the 20 other static ctors. Don't know if this will cause conflicts with the current way module dependencies work, though. T -- Кто везде - тот нигде.
Mar 27 2013
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 27 Mar 2013 11:34:19 -0400, Vidar Wahlberg  
<vidar.wahlberg gmail.com> wrote:

 - When casting a value to an enum, there's no checking that the value  
 actually is a valid enum value. Don't think I ever found a solution on  
 how to check whether the value after casting is a valid enum value, it  
 hasn't been a pressing issue.
Typically, one uses std.conv.to to safely convert one value into another. Cast should be avoided unless absolutely necessary. I just tested it, it works on enum *strings*, but not enum *values* For example: import std.conv; enum X { i, j, k } void main() { X x = to!X("i"); // works! x = to!X(1); // fails! } I think to should be able to do this, but I'm not a good enough guru with templates and compile-time type info to know if it's possible. Anyone know if this is possible? If so, I think it should be added. -Steve
Mar 27 2013
next sibling parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 13:08:19 -0400
 
 I just tested it, it works on enum *strings*, but not enum *values*
 
 For example:
 
 import std.conv;
 
 enum X {
   i, j, k
 }
 
 void main()
 {
     X x = to!X("i"); // works!
     x = to!X(1); // fails!
 }
 
Works for me on 2.063 Win. Keep in mind: assert(cast(int)X.i == 0); assert(cast(int)X.j == 1);
Mar 27 2013
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Wed, 27 Mar 2013 14:07:27 -0400
Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> wrote:

 On Wed, 27 Mar 2013 13:08:19 -0400
 
 I just tested it, it works on enum *strings*, but not enum *values*
 
 For example:
 
 import std.conv;
 
 enum X {
   i, j, k
 }
 
 void main()
 {
     X x = to!X("i"); // works!
     x = to!X(1); // fails!
 }
 
Works for me on 2.063 Win. Keep in mind: assert(cast(int)X.i == 0); assert(cast(int)X.j == 1);
I meant of course 2.062
Mar 27 2013
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 27 Mar 2013 14:09:07 -0400, Nick Sabalausky  
<SeeWebsiteToContactMe semitwist.com> wrote:

 On Wed, 27 Mar 2013 14:07:27 -0400
 Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> wrote:

 On Wed, 27 Mar 2013 13:08:19 -0400
 I just tested it, it works on enum *strings*, but not enum *values*

 For example:

 import std.conv;

 enum X {
   i, j, k
 }

 void main()
 {
     X x = to!X("i"); // works!
     x = to!X(1); // fails!
 }
Works for me on 2.063 Win. Keep in mind: assert(cast(int)X.i == 0); assert(cast(int)X.j == 1);
I meant of course 2.062
Hah, I have not yet downloaded 2.062. It did not work in 2.061, not sure if that was a bug or it's a new feature. anyway, that is good to know! -Steve
Mar 27 2013
parent 1100110 <0b1100110 gmail.com> writes:
On 03/27/2013 03:47 PM, Steven Schveighoffer wrote:
 On Wed, 27 Mar 2013 14:09:07 -0400, Nick Sabalausky
 <SeeWebsiteToContactMe semitwist.com> wrote:

 On Wed, 27 Mar 2013 14:07:27 -0400
 Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> wrote:

 On Wed, 27 Mar 2013 13:08:19 -0400
 I just tested it, it works on enum *strings*, but not enum *values*

 For example:

 import std.conv;

 enum X {
 i, j, k
 }

 void main()
 {
 X x = to!X("i"); // works!
 x = to!X(1); // fails!
 }
Works for me on 2.063 Win. Keep in mind: assert(cast(int)X.i == 0); assert(cast(int)X.j == 1);
I meant of course 2.062
Hah, I have not yet downloaded 2.062. It did not work in 2.061, not sure if that was a bug or it's a new feature. anyway, that is good to know! -Steve
It worked for me in 2.061, as that's what I'm currently on. Waiting to build the release that I hear might have shared objects!
Mar 28 2013
prev sibling parent 1100110 <0b1100110 gmail.com> writes:
On 03/27/2013 12:08 PM, Steven Schveighoffer wrote:
 On Wed, 27 Mar 2013 11:34:19 -0400, Vidar Wahlberg
 <vidar.wahlberg gmail.com> wrote:

 - When casting a value to an enum, there's no checking that the value
 actually is a valid enum value. Don't think I ever found a solution on
 how to check whether the value after casting is a valid enum value, it
 hasn't been a pressing issue.
Typically, one uses std.conv.to to safely convert one value into another. Cast should be avoided unless absolutely necessary. I just tested it, it works on enum *strings*, but not enum *values* For example: import std.conv; enum X { i, j, k } void main() { X x = to!X("i"); // works! x = to!X(1); // fails! }
 I think to should be able to do this, but I'm not a good enough guru
 with templates and compile-time type info to know if it's possible.
 Anyone know if this is possible? If so, I think it should be added.

 -Steve
It also works on values.... enum A { B, C, D, } int w; import std.conv; import std.stdio; void main() { auto t = w.to!A.B; writeln(t); }
Mar 28 2013
prev sibling next sibling parent "Jesse Phillips" <Jessekphillips+D gmail.com> writes:
On Wednesday, 27 March 2013 at 15:34:20 UTC, Vidar Wahlberg wrote:
 I know I'm probably going to upset some people with this, 
 bashing their favourite child and all, but I wanted to let you 
 know the experience I've had with D so far, as a novice D coder 
 with a heavy Java & light C++ background.
I think D has quite a strong set of critic users, namely because the benefits are just too great. Not to suggest less importance to these issues, but these are known issues. It is good to have them brought up from the new user perspective.
 Woes:
 -----
 - I find myself in a world of pain when I want to share data
I don't use this, but my understanding is that 'shared' is a low level feature. In general one doing threading should never touch it and instead a library is built from it. The problem is we don't have a good threading library, we have parallelism and concurrency which may use threads but not always what one would expect when looking for threading. I could be completely wrong here!
 - While the "auto"-keyword often is great, it can lead to 
 difficulties, especially when used as the return type of a 
 function, such as "auto foo() { return bar; }". Sometimes you 
 may wish to store the result of a function/method call as a 
 global variable/class member, but when the function/method 
 returns "auto" it's not apparent what the data type may be.
My observation is that many times in D the type is something you probably don't want to write out even if you know it. Using typeof() tends to be a better choice even if it isn't straight forward.
 Gotchas:
 --------
 - The lack of "rectangular" arrays created at runtime in D 
 ("int i = 5; int[i][i] foo;") can be quite confusing for 
 programmers with Java or C++ background.
Well in this case you are trying to create a static 2D array with a runtime value. I haven't had issues with 2D arrays, but I'm not familiar with "rectangular" arrays.
 - Static array versus dynamic array was one of the first traps 
 I stepped on
Static arrays are very hidden. Much like arrays are hidden in C.
 - When casting a value to an enum, there's no checking that the 
 value actually is a valid enum value. Don't think I ever found 
 a solution on how to check whether the value after casting is a 
 valid enum value, it hasn't been a pressing issue.
This would be nice, but enum's are also nice for binary flags. auto option = My.A | My.B | My.C; option would be a valid My for how it is used, but it is not defined as valid. There has been discussions on alternatives to using an enum (but enum is convenient).
 - Compiling where DMD can't find all modules cause a rather 
 cryptic error message. A solution is to make sure you specify 
 all source files when compiling.
The compile => link process is pretty well hidden in most languages these days (many don't have it).
 Wishlist:
 ---------
 - "void[T]" associative array (i.e. a "set") would be nice, can 
 be achieved with "byte[0][T]".
I think a proper library container would be fine.
 - "Foo foo = new Foo();" for global variables/class members. 
 Now you must "Foo foo; static this() { foo = new Foo(); }".
This won't happen. It is kind of a feature. Otherwise you'd be confused with the static this dependency error because "I'm not using static this."
Mar 27 2013
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
27-Mar-2013 19:34, Vidar Wahlberg пишет:
 I know I'm probably going to upset some people with this, bashing their
 favourite child and all, but I wanted to let you know the experience
 I've had with D so far, as a novice D coder with a heavy Java & light
 C++ background.
 It's not that I dislike D, in fact there are tons of things I love about
 it, it's pretty much exactly what I'm looking for in a programming
 language at the moment. Yet, I encounter some frustrating issues when
 coding, often leaving me with the impression that I'm fighting the
 language more than the problem I'm trying to solve.
 True, there are many things I don't know about D, compilers or the inner
 workings of a computer, and some of the fights I have with the language
 are likely started by myself because I'm dragging along my bias from
 other languages, drawing misconceptions on how the D language actually
 works.
 My intentions are not to insult, but shed some light on some of the
 difficulties I've faced (and how I solved them), with the hope that it
 will help others from facing the same difficulties.


 Woes:
 -----
 - I find myself in a world of pain when I want to share data more
 complex than the basic data types (int, char, byte, etc) across threads.
 Seemingly the magic trick is to "cast(shared) foo" (or
 "cast(immutable)") when passing objects/references to another thread,
 then "cast(Foo)" back on the receiving end (as most classes/structs in
 the standard library refuse to let you call any methods when the object
 is shared). The examples in the source and TDPL are fairly limited on
 the issue, it mostly covers only those basic data types.

 - While the "auto"-keyword often is great, it can lead to difficulties,
 especially when used as the return type of a function, such as "auto
 foo() { return bar; }". Sometimes you may wish to store the result of a
 function/method call as a global variable/class member, but when the
 function/method returns "auto" it's not apparent what the data type may
 be. While you may be able to find out what "bar" is by digging in the
 source code, it can still be difficult to find. One example is to save
 the result of "std.regex.match()" as a member in a class. For me the
 solution was to "import std.traits", create a function "auto
 matchText(string text) { return match(text, myRegex); }" and define the
 class member as "ReturnType!matchText matchResult;" (do also note that
 function & member must come in the right order for this to compile).
Currently documentation for match states in plain text that: " ... Returns: a RegexMatch object holding engine state after first match. " Where RegexMatch is a template to be found in the the same module. I can see that docs are nothing stellar but the key info is present. That being said I see no problem with ReturnType!xyz, typeof(smth) or just typing auto.
 This was all but obvious to a novice D coder as myself, the solution was
 suggested to me in the IRC channel.


 Gotchas:
 --------
 - The lack of "rectangular" arrays created at runtime in D ("int i = 5;
 int[i][i] foo;") can be quite confusing for programmers with Java or C++
 background.
The code you suggested doesn't work in both C and Java.
 Even though there exists alternatives
 (http://denis-sh.github.com/phobos-additions/unstd.multidimensionalarray.html),
 this design decision and how to get around it when you really desire a
 "rectangular" array could be explained in more detail at
 http://dlang.org/arrays.html.
IRC you can get jagged arrays with the syntax: int[][] arr = new int[][](x,y);
 - Compiling where DMD can't find all modules cause a rather cryptic
 error message. A solution is to make sure you specify all source files
 when compiling.
Linker... there are many ways to improve on that old technology that but not much have been done. And the problem is not D specific. There is a tool rdmd that tracks all dependencies: rdmd main_module.d That plus: rdmd --build-only main_module.d Is more then enough in 90% of cases for me.
 Wishlist:
 ---------
 - "void[T]" associative array (i.e. a "set") would be nice, can be
 achieved with "byte[0][T]".
We need a better set anyway as hash-map is a good map but suboptimal as a set. -- Dmitry Olshansky
Mar 27 2013
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/27/13 11:34 AM, Vidar Wahlberg wrote:
 I know I'm probably going to upset some people with this, bashing their
 favourite child and all, but I wanted to let you know the experience
 I've had with D so far, as a novice D coder with a heavy Java & light
 C++ background.
[snip] We're very interested (and therefore grateful) in hearing experience reports. There have been a number of responses already so I'll just insert a couple of brief points. * The MSB I'm seeing for all of these grievances is that we must improve our documentation. * We used to have reference semantics for static arrays, it was a nightmare. * As mentioned, "shared" is a last-resort, intentionally limited of communicating between threads. We must improve our message passing infrastructure (including documentation and examples). Definitely we must finish all details of what shared means (and doesn't). This remains largely an educational issue because people coming from share-intensive language expect to use shared casually (much as seasoned C++ users expected to use const casually; this particular issue has been largely resolved). * We need to have a battery of multidimensional array shapes along with simple iteration and access primitives, at least for interfacing with scientific libraries that define and expect such formats. I'm thinking rectangular (generally hyperrectangular) matrices, triangular matrices, sparse matrices, and band matrices. Thanks again for sharing your thoughts. Andrei
Mar 27 2013
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Mar 27, 2013 at 04:22:00PM -0400, Andrei Alexandrescu wrote:
[...]
 * We need to have a battery of multidimensional array shapes along
 with simple iteration and access primitives, at least for interfacing
 with scientific libraries that define and expect such formats. I'm
 thinking rectangular (generally hyperrectangular) matrices, triangular
 matrices, sparse matrices, and band matrices.
[...] Given that different computational needs will require different implementations, it might be a good idea to define a generic set of templates for identifying something as a multi-dimensional array, and adopt some conventions as to how things are named (e.g., .dimensions[i] to retrieve the length of the i'th dimension of the array, or the fact that .opIndex(a,b,c,...) is defined, etc.). The idea is that we want to implement some generic array algorithms that don't care about whether it's a dense array, triangular matrix, sparse array, etc.. These should be independent of the actual storage format. There are, of course, algorithms that will benefit from specific implementations (e.g. algorithms that take advantage of the sparseness of a matrix, say), so those will specificially depend on, say, a sparse array. Generic identification of array types is also necessary to maximize interoperability between computational libraries. For one thing, I do *not* wish to see a repeat of the C++ situation where the multidimensional array types between different libraries are incompatible and require expensive copying and/or layers upon layers of wrappers just to interoperate. The situation in D should be such that any library's array type should be interoperable with any other library's array type, as long as both satisfy the standard array-identification templates. In a nutshell, multidimensional arrays should have a standard API so that any multidimensional array can be used with any algorithm that expects one. T -- Always remember that you are unique. Just like everybody else. -- despair.com
Mar 27 2013
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2013-03-27 16:34, Vidar Wahlberg wrote:

 - I find myself in a world of pain when I want to share data more
 complex than the basic data types (int, char, byte, etc) across threads.
 Seemingly the magic trick is to "cast(shared) foo" (or
 "cast(immutable)") when passing objects/references to another thread,
 then "cast(Foo)" back on the receiving end (as most classes/structs in
 the standard library refuse to let you call any methods when the object
 is shared). The examples in the source and TDPL are fairly limited on
 the issue, it mostly covers only those basic data types.
Try message passing and serialize the data you want to send. Message passing: http://dlang.org/phobos/std_concurrency.html Serialization: https://github.com/jacob-carlborg/orange -- /Jacob Carlborg
Mar 28 2013