Re: Best practices of using const
On Wednesday, 13 February 2019 at 16:40:18 UTC, H. S. Teoh wrote: So ironically, the iron-clad semantics of D's const system turns out to be also its own downfall. Such things are not ironic. There is always a trade off. You get nothing for free in this universe. Physics tells us this. Conservation laws apply to energy and everything is energy. Hence your computer cannot violate these laws nor can the D specification(whatever it ends up meaning) nor can the D const system, so to speak... That is, any time there something is inversely related to something else then there will be a conservation relationship. If you restrict something too much then something else in direct opposition is becoming too unrestricted. It's not that D's const system is bad, it is that it creates too much restriction without any other option. The usual way too solve these problems is granularity. this way you can choose the right tool for the job. Maybe D needs different levels of const. constN where constN can always be cast to constn for n <= N. One would need to properly define the levels to maximize utility and minimize the granularity. D probably only needs 3-5 levels to be effective.
Trick for passing void* arrays around with typing
foo(cast(void*)[Object1, Object2]); foo(cast(bool delegate(void*))(Tuple!(X,Y) objects)) { }); One can pass arbitrary data as an array(the cast is ugly though) Then the tuple is cast back to void* but one can access objects correctly with typing. This works well when interacting with C/C++ void* data passing but can also work with passing heterogeneous objects in general. import std.stdio, std.typecons; class X { int x = 1; } class Y { int y = 2; } // a ** is required for the cast. void foo(void** data) { auto dd = cast(Tuple!(X,Y)*)(data); auto d = *dd; writeln(d[0].x); writeln(d[1].y); } // This would be a C callback that is hidden from us but the callback isn't. alias Q = void delegate(void*); void bar(Q d, void* x) { d(x); } void main() { X x = new X; Y y = new Y; foo(cast(void**)[x, y]); bar(cast(Q)(Tuple!(X,Y)* dd) { auto d = *dd; writeln("> ", d[0].x); writeln("> ", d[1].y); }, cast(void*)[x, y]); } What would be nice is if one didn't actually have to jump through all the hoops: void foo(void* as Tuple!(X,Y) d) // Redundant here but only because we have access to foo { writeln(d[0].x); writeln(d[1].y); } // This would be a C callback that is hidden from us but the callback isn't. alias Q = void delegate(void*); void bar(Q d, void* x) { d(x); } void main() { X x = new X; Y y = new Y; foo([x, y]); bar((as Tuple!(X,Y) d) { writeln("> ", d[0].x); writeln("> ", d[1].y); }, as [x, y]); } here I'm using as to signify a sort of implicit cast. The main thing to note is that for the callback, D gets the type as it is defined even though the cast changes the overall delegate to what it should be: bar(cast(Q)(Tuple!(X,Y)* dd) { auto d = *dd; writeln("> ", d[0].x); writeln("> ", d[1].y); }, cast(void*)[x, y]); } I don't actually use * in my code I think it is necessary to get the right values due to how they are passed. I think it has to do with register vs stack passing. So it might be fragile What I have is bar(cast(Q)(Tuple!(X,Y) d) { writeln("> ", d[0].x); writeln("> ", d[1].y); }, cast(void*)[x, y]); } which works, at least for x64. The cool thing is the "casting" all happens internally and so there is no direct need for casting. Obviously though there is no real casting going on(one could swap x and y in the argument) and so it is dangerous... but D could make it 100% type safe with the appropriate syntax. (only for delegates and callbacks) although maybe one could use it for normal functions too: foo([x,y] as Tuple!(X,Y)) e.g., normally we would have to do bar((dd) { auto d = cast(Tuple!(X,Y))dd; // Doesn't work though because tuple is not a reference, so must use the double pointer hacks. writeln("> ", d[0].x); writeln("> ", d[1].y); }, cast(void*)[x, y]); } When working with quite a number of C callbacks and passing data, this is a nice pattern to use to avoid the ugly temp variables and casting.
Component based programming in D
I'm new to component based programming. I've read it is an alternative to oop for speed. I don't understand how it is possible to have an alternative to oop and still have oop like behavior(polymorphism) nor how to do this. It seems all the great things oop offers(all the design patterns) would be impossible. The benefits are suppose to better reusability as components are more isolated in their dependencies. Can someone help me understand this a little better and how I'd go about using it in D? Specifically I'm looking at the pros and cons, what are the real similarities and differences to oop, and how one implements them in D(taking in to account D's capabilities). Thanks.
Regex driving me nuts
Error: static variable `thompsonFactory` cannot be read at compile time, Trying to regex an import file. Also I have a group (...)* and it always fails or matches only one but if I do (...)(...)(...) it matches all 3(fails if more or less of course. ... is the regex). Also when I ignore a group (?:Text) I get Text as a matched group ;/ But all this is irrelevant if I can't get the code to work at compile time. I tried ctRegex // A workaround for R-T enum re = regex(...) template defaultFactory(Char) { @property MatcherFactory!Char defaultFactory(const ref Regex!Char re) @safe { import std.regex.internal.backtracking : BacktrackingMatcher; import std.regex.internal.thompson : ThompsonMatcher; import std.algorithm.searching : canFind; static MatcherFactory!Char backtrackingFactory; static MatcherFactory!Char thompsonFactory; if (re.backrefed.canFind!"a != 0") { if (backtrackingFactory is null) backtrackingFactory = new RuntimeFactory!(BacktrackingMatcher, Char); return backtrackingFactory; } else { if (thompsonFactory is null) thompsonFactory = new RuntimeFactory!(ThompsonMatcher, Char); return thompsonFactory; } } } The workaround seems to workaround working.
CT/RT annoyance
Consider void foo(string A = "")(string B = "") { static if (A != "") do(A); else do(B); } foo!("x"); foo("x"); This is necessary because D's templating and meta programming system is frail. While CTFE should take care of such things, it does not, consider import(file) vs readText(file). If do loads such a file then one must use tricks as above: auto load(string n = "")(string n2 = "") { static if (n != "") enum x = import(n); else auto x = readText(n2); } auto load2(string n) { enum x = import(n); string y = import(n); } load2 fails to compile SIMPLY because n is a RT parameter. Even if ctfe were to kick in, such as load2("LiteralFileName.txt"), it is irrelevant because we can't compile the code. So tricks as in load are used, but it is very hacky and verbose to do something very simple and natural. This is precisely because readText cannot be used at compile time since it dispatches to the OS's reading routines rather than using import. This is not necessarily bad since we generally do not want to import files in to the application at compile time, but when we do it creates somewhat of a mess to unify code under ctfe. For example, if I create a function that loads a file and processes it at RT then that code will not compile when used in CTFE. If I modify it to import the text at CT then it does not work at RT... even though the single point of breakage is the import/readText line. auto importOrReadText(string n) { if (__ctfe) { auto x = import(n); } else { auto x = readText(n); } } Fails for the same reasons above. There is no way to get out of this conundrum(and therefor it is a contradiction). It thin requires kicking the can down the street and not read files at all but take text. My suggestion is for someone to fix this. I'm not sure of the solution. maybe something like auto importOrReadText(string n) { if (__ctfe) { auto x = import(ctfeFix(n)); } else { auto x = readText(n); } } ctfeFix would be a special compiler semantic that takes a RT-like variable and converts it to CT. It does this because it actually can. It knows that n is ctfe'able and therefore n is actually a string literal and will do some "magic" trick the compiler in to seeing n for what it really is. importOrReadText(rtFilename); // uses readText importOrReadText("x.txt"); // uses import Unfortunately __ctfe doesn't seem to actually work well... so I suggest further auto importOrReadText(string n) { static if (isKACT(n)) { auto x = import(n); // or auto x = import(ctfeFix(n)); } else { auto x = readText(n); } } isKACT determines if n is actually known at compile time and marks it n as essentially a CT constant as if it were passed as a template parameter(internally it would probably just convert it to a template parameter as if it were passed as such). There may be other solutions.