UFCS is such an underrated language feature. When you have UFCS you can toss out 90% of the uses of methods in favor of just plain ole functions. Add generic functions and concepts and you rarely end up needing OO support.
Dot syntax tends to work better for code completion, though.
In addition, without uniform call syntax, adding a new method can only break subclasses, whereas with uniform call syntax it can break other client code.
My comment was about the reverse, using function syntax for methods.
Furthermore, I don’t think it necessarily makes sense for all functions that happen to take, say, a string as their first argument, to be listed in the code completion for method invocation on a string variable.
If you merely want to define auxiliary methods outside of a class, which is the thing the GP seems to like, that’s what’s usually called “extension methods”. It doesn’t require uniform call syntax.
hmm, yeah fair enough I suppose. I don't think I've found a good use-case for that yet. I guess having the symmetry there makes the feature easier to explain at least? I dunno.
> Furthermore, I don’t think it necessarily makes sense for all functions that happen to take, say, a string as their first argument, to be listed in the code completion for method invocation on a string variable.
All functions in scope that happen to take a string as their first argument. If this turns into an actual problem in practice it's quite doable to refactor things such that it's not an issue.
I find that when I use autocomplete I'll be typing the first bit of the method name in any case. I never "browse" autocomplete to look for the thing I need.
Extension methods are another way to do the same thing yes, but that feels like special-casing behavior, where UFCS is more general. With extension methods you need to think of how to implement it wrt things that don't usually have methods attached. With UFCS that just works the way you'd expect it to.
I always find it funny how people focus on the .method() syntax. You have the "pipe" operator in some functional languages that achieves the same thing, pass the result of an expression as the first argument of the chained call on the right. Nothing to do with OOP.
I think it needs a higher level experience across procedural, logical, functional and object based languages, the unique ways each one applies ideas into their programming model, how that interacts to CS, for finally understanding how those concepts come together, instead of being so fixated in language syntax.
`private` is only private to the module, not the struct/class, (In other words, all functions in the same module are all C++ style `friend`s) and so free function in same module work.
It's a mystery why D isn't far more popular than it is. Fast compilation, familiar syntax, and supports a wider range of programming paradigms than most (any?) other language.
> It's a mystery why D isn't far more popular than it is.
There's no mystery. It's a jack of all trades, master of none. E.g., the virality of the GC makes it a non-starter for its primary audience (C/C++ developers). The need to compile and the extra verbosity makes it a bad substitute for scripting like Python. Etc.
Basically, name any large niche you'd expect it to fill and you'll probably find there's a tool already better suited for that niche.
That's kind of why I said it's the "jack of all trades". It's not a bad language, it just doesn't beat any existing languages I know of in their own niches (hence "master of none"), so few people are going to find it useful to drop any existing languages and use D in lieu of them.
> the virality of the GC makes it a non-starter for its primary audience (C/C++ developers)
No. If you were to say you need the GC to use all features of the language and standard library, of course, the GC does important things, but to claim a C developer wouldn't be comfortable with it because of the GC is nonsense. Just don't allocate with the GC and use the same mechanisms you'd use with C (and then build on top of them with things like @safe, reference counting, and unique pointers).
> If you're a C programmer and want to avoid the GC, there's nothing to be viral.
What you're suggesting is the moral equivalent of "it's easy to avoid diseases, just avoid contact with those infected", or "it's easy to avoid allergens, just avoid foods you're allergic to", or "it's easy to avoid contamination, just set up a cleanroom", or "it's easy to write deterministic code, just avoid randomness", etc.
Yes, there are things that are easy to achieve in the vacuum of outer space, but that's not where most people are interested in living.
According to them[0], D as better C is indeed just C with less footguns and some additional optional features like RAII (that one can use or not) or more comptime assumptions (again, that one can use or ignore).
I don't think what hinders their adoption is their direction, everything they say they accomplished/plan to accomplish is ideal IMO.
> D as better C is indeed just C with less footguns and some additional features like RAII (that one can use or not) or more comptime assumptions (again, that one can use, or ignore)
Having strictly more features (if we even assume that, which I don't think is accurate) does not imply better.
Javascript is just JSON with more features too. Is it a mystery that people don't ship Javascript code everywhere instead of JSON?
Completely agree after trying it. Anything that may throw an Exception requires GC. There goes 80% of D code you could use. The rest becomes inaccessible for other, similar reasons quite often. Try writing D with @nogc and it takes 10 minutes to understand that. They want to make the situation better but there’s just not enough people to tackle the huge amount of work that requires (I.e. changing most of the stdlib)
> Try writing D with @nogc and it takes 10 minutes to understand that.
Thank you. Yes, exactly. The problems aren't even subtle; it's impossible to miss them if you actually try. I don't recall even finding a reasonable way to concatenate or split strings on the heap and return them without a GC, let alone anything more complicated. It boggles my mind that people repeat the talking point that it's somehow practical to program with @nogc when the most basic operations are so painful. Nobody is going to drool at the idea of spending days/weeks of their lives reinventing nonstandard second-class-citizen counterparts to basic types like strings just to use a new language.
> They want to make the situation better but there’s just not enough people to tackle the huge amount of work that requires (I.e. changing most of the stdlib)
I don't agree that it's lack of manpower that's the problem -- at least, not yet. I think it's primarily the unwillingness to even admit this is a problem (an existential problem for the language, I think) and confront it instead of denying the reality, and secondarily the inertia and ecosystem around the existing language outside the standard library. It's not like the problem is subtle (like you said, a few minutes of coding makes it painfully obvious) or novel. The language has been out there for over a decade and a half, and people have been asking for no-GC version nearly that long. Yet, at least to the extent I've had the energy to follow it, the response has always been the canned you-can-totally-program-D-without-a-GC denials you see repeated for the millionth time here, or (at best) silence. If this sentiment has changed and I'm unaware of it, that's already significant progress.
Maybe the unwillingness to confront reality is due to the lack of manpower and how daunting the problem looks; I'm not sure. But it seems as bright as daylight that D is not going to be successful without solving this problem.
I tend to not use this sort of function because it doesn't manage its own memory. I use barray instead because it manages its memory using RAII. D provides enormous flexibility in managing memory. Or, you can just leave it to the gc to do it for you.
I feel you're demonstrating exactly the problems I highlighted through your example here -- including the very lack of acknowledgment of the overall problem.
Part of the problem it's trying to do too many things in too many fronts.
They try to implement a borrow-checker a la Rust. But feels very poorly compared against the Rust version.
It haves a "optional" GC, but it's a subpar GC. And lacks a way to use alternative GCs.
And funny, C++ has been copying many features that DLang have for many time ago. Beginning with the type inference (ie using "auto" to declare vars). And now, contractual programing and static reflection.
I really loved the language, but it's very sad that never manages to take off and become more popular and well maintained.
When you use many languages, don't you become a jack of all trades, master of none? Also it's not obvious python is good for scripting, bash is better at short scripts, statically typed languages are better at long scripts, and you can't use it in CI yaml scripts. Python is more famous for data science and AI, not for scripting.
I used "scripting" loosely (Python source files are frequently called Python scripts), I wasn't referring to shell scripts specifically. Feel free to pretend I said Bash or data science or whatever you want.
Re: your first sentence: I neither understand the logic nor do I understand how insulting the developer is going to help D succeed here even if the logic was sound.
I almost mentioned Go as another example of what it doesn't substitute for, but decided to cut off the list. But no, I don't think it fits that niche either. Go has a lot of features D doesn't have. Just off the top of my head: it's very simple, it doesn't have pointer arithmetic (yes, that's a feature here), it performs escape analysis to automatically extend the lifetime of objects that escape the stack frame, etc.
D does not allow pointer arithmetic in code marked @safe.
D does escape analysis from an alternative direction. If a pointer is qualified with `scope`, the compiler guarantees it does not escape the stack frame.
That code is an example of f(a) being equivalent to a.f(). You can do it the f(a) way if you prefer.
`map` is an operation on a data structure that replaces one element with another, in this case `a` gets replaces with `idup(a)`. The `idup` makes a copy of its argument in memory, and marks the data is immutable.
> That code is an example of f(a) being equivalent to a.f(). You can do it the f(a) way if you prefer.
How would it look like with this particular code? Just for comparison.
> The `idup` makes a copy of its argument in memory, and marks the data is immutable.
How is one supposed to know this? Reading the documentation? I really want to look at the code and be able to know straight away what it does, or have a rough idea.
It has a fairly direct analogy in some languages. A C♯ programmer reading the above will immediately recognize the analogy to .Select(), .ToArray(), and so forth from LiNQ.
Plus the chicken and the egg problem. This is mostly from the AerynOS experience : it seems like if you want to write some moderately complicated code then you're becoming the upstream of many libraries. Especially now with Rust's popularity and ecosystem maturity on the rise, it's super hard to convince people (e.g. your boss) that you'd be better of with D compared to e.g. Rust.
I spent quite a few year on/with it back in the day. There was D1 which was like a better C, and then there was D2 which was like a better C++. Personally I preferred where D1 was going (and Tango instead of Phobos) but even with D2 it really made the day compared to what was out there and to this day still to an extent is. The thing that killed it for me, and I know at least a couple of friends as well (outside of internal politics at a time) was what kills pretty much all exotics once you start using it. Lack of (up-to-date) libraries / bindings and tooling. At the end of the day that's what you do use for most of the work you're doing anyways - libraries. So suddenly you're doing all these bindings and battling tools instead of working on actual problem at hand. This gets tiresome real quick.
For some reason, and mostly that being Mozilla, Rust got quite an initial kick to overcome that initial hurdle in haste. We're not going to mention a lot of those libs are stale in Rust world, but at least they're there and that kind of gives you momentum to go forward. Whatever you're trying to do, there's a non-zero chance there's a library or something out there for you in Rust.. and we got there real quick which then encouraged people to proceed.
That's just like my opinion, man.. but I think a key part is that first lib bindings hurdle which Rust somehow went over real quick for a critical mass of it; D hasn't.
Love the D though lol, and Walter is a 10000x programmer if you ever saw one but it might be time to hang the hat. I can only imagine how a community like Rust or I don't know Zig of those up-and-coming would benefit from his help and insights. He'd probably single-handedly make rust compile 100x faster. One can hope.
I've heard nice things about Zig being a more ergo alternative to Rust, but I haven't seen anyone compare it to D yet. From my brief testing, it seemed like Zig wasn't as ergo as D, but in theory it could evolve to maybe get there. From the outside, it doesn't seem like Zig has made any super major ergo improvements in the last year, but I could be wrong.
I don't know if I would describe Zig as ergonomic per se. It has some nice features, but the main focus is on completely explicit control over low-level details. In its design Zig always chooses explicitness and programmer control over ergonomics. If a language feature requires a lot of compiler magic then it's probably never going to be added.
The D parts of the compiler were released under the GPL from almost the beginning, since 2002. By 2004, a full open source compiler - what we now call gdc, officially part of gcc - was released using this GPL code. D was pretty popular in these years.
Articles like this I guess. If these are the 'lovable' features I'd hate to see the 'meh' features.
Automatic constructors - You only have to write the 'make me a box of two apples' code and not 'this is how two apples go into a box'! This is as revolutionary as 'automatic function calls', where you don't have to manually push the instruction pointer to pop it back off later.
Parenthesis omission!
If I were to parody this I'd talk about how good Scala is - in addition to classes, you can also declare objects, saving you the effort of typing out the static keyword on each member.
Sell me something nice! Millions of threads on a node. Structured concurrency. Hygienic macros. Homoiconicity. Higher-kinded types. Decent type inference. Pure functions. Transactions.
scala is probably the poster child for supporting every paradigm you might want to use :) oz/mozart has more but that was essentially a research/teaching language specifically designed to use a wide range of paradigms in order to demonstrate them.
The "invariants" thing is fantastic, I haven't seen anything like that before and it's great. The C++26 contract stuff is fine, but this seems like a really great way of ensuring type invariants, I think I'd use this way more if it was in C++.
The issue is most developers do not bother to write any, and the ones that are written are most-often vapid typing failures ("these`int`s cannot be negative" should be handled by a type). I studied this field in grad school, and the entire problem almost always devolves into convincing developers to engage with the system.
I find that is the case with almost all methodologies for software quality improvement. If you can't enforce that people follow it then it's not worth anything.
I feel like this feature could be implemented on top of more universal features.
Checking input parameters is easy, just write asserts at the start of the function.
Checking result requires "destructor" block and some kind of accessible result variable, so you can write asserts in this destructor block which you can place at the start of the function, as well.
Checking class invariants requires a way to specify that some function should be called at the end of every public function. I think, it's called aspect-oriented programming in Java and it's actually useful for more things, than just invariant checking. Declarative transaction management, logging.
There are probably two schools of programming language designs. Some put a lot of features into language and other trying to put a minimal number of features into language which are enough to express other features.
Having the higher level abstraction built into the language gives extra semantic meaning that can be taken advantage of to build tooling. For example, one could build a symbolic model checker based on the contract specifications. It would be possible to do the same with the lower level features, but a lot harder if they aren't used consistently and correctly.
Same reason function calls are better than arbitrary jumps.
Just a personal anecdote, Walter Bright's Digital Mars C++ compiler also had the contracts (D started life almost literally as recycled code from Mr. Bright's other compilers - he wrote a native Java compiler, a Javascript 1.3 stdlib, and a C++ compiler with a bunch of extensions.... smash those together and you have the early D releases!).
Anyway, I used the DM C++ compiler originally because it was the only one I could download to the high school computers without filling out a form, and pimply-face youth me saw "DESIGN BY CONTRACT" at the top of the website and got kinda excited thinking it was a way to make some easy money coding online.
Imagine my disappointment when I saw it was just in/out/invariant/assert features. (I'm pretty sure D had just come out when I saw that, but I saw `import` instead of `#include` and dismissed it as a weenie language. Came back a couple years later and cursed my younger self for being a fool! lol)
`import` is so cool we extended it to be able to import .c files! The D compiler internally translates them to D so they can be used. When this was initially proposed, the reaction was "what's that good for?" It turned out to be incredibly useful and a huge time saver.
The concept is sort of like C++ being a superset of C and so being able to incorporate C code, except unlike C++, the C syntax can be left behind. After all, don't we get tired of:
What's the thing with the syntax? If you don't intend to use the type elsewhere don't give it a tag, if you want, you have to give it a name. (Assuming you are annoyed by the duplicate Tag)
const MIN_U32 = 0;
const MAX_U32 = 2 ** 32 - 1;
function u32(v) {
if (v < MIN_U32 || v > MAX_U32) {
throw Error(`Value out of range for u32: ${v}`);
}
return leb128(v);
}
You can do this, in Ada:
subtype U32 is Interfaces.Unsigned_64 range 0 .. 2 ** 32 - 1;
Yeah, these look excellent. Am curious if D's invariants can be traced back to Ada/Spark at all (I don't know much about Ada except that it has these sorts of safety features).
Yes, but they are guaranteed to run at the beginning and end. C/C++ asserts need to handle any return path, whereas D has functionality to mark statements to run at the end of any return path while only being written once.
You define the invariants once for the class and they are run around every public function. Done manually you'd probably use a helper object that calls the invariants in its constructor and destructor (have to handle exceptions) that you have to add to every public function's definition.
Has anyone compared D and Zig? I originally learned D over one weekend and then went on to completed several code competitions- the ergonomics of D are just fantastic.
I like D, it's fascinating and powerful language. It made it even more curious when I watched Tsodings video on D. One thing that came to my mind when reading the article is that things like int.init instead of 0 and $ as shorthand for array.length does add to the mental load.
One good memory I had is a couple of years ago when I built a little forum using D. Man the site was blazing fast, like the interaction was instant. Good times.
In my opinion, some features of D are good, but I do not like all of them.
CTFE is good.
I do not really like the UFCS; if you want it to be used like a member of the first parameter then you should define it as a member of the first parameter (possibly as a inline function that only calls the freestanding function with the same name, if that is what you want it to do). (You could use a macro to do this automatically if you want to.)
Scoped imports is good, but I think that scoped macros would also be helpful.
Exhaustive switch seem like it might be better if designed differently than it is, but the idea seems to be not bad, in general.
Honestly the main reason UFCS is good is when you don't "own" the type your function would need to be a member of. Most common one I run into is the "to" function from the stdlib. You can do this:
import std.conv;
int foo=3;
string bar = foo.to!string
But now lets say you want to convert ints to MyCustomInts? You can hardly attach a new "to" member to int, but with UFCS it's easy. Just declare a to function anywhere in scope:
MyCustomInt to(T)(T v) if(is(T==int)){
return MyCustomInt.from_int_value(v)
// or however you actually do the conversion
}
that actually wasn't its intended use; that's a side effect. The original intended use came from Effective C++ by Scott Meyers: "Prefer non-member non-friend functions to member functions.". It was meant to make that as syntactically appealing as the members.
When I last looked at D they did a lot of interesting stuff with compile time function execution and s.th. like dependent types that they hacked into the language.
Can someone give me an update about the current state of this?
I really enjoy these lists of interesting features from various languages. They pop up occasionally on HN but now I can’t find them (Hillel Wayne had multiple).
I want a meta list of all these interesting features across languages.
In concrete, looks to me to be the only language that covers the major ways to do it.
(In concrete the `scope` way is the one I found inspiring. . I think the exceptions can go and be replace by it for langs where exceptions are removed)
`scope` is very good at its job. It guarantees that a pointer passed as an argument does not escape the caller's scope. I find it almost as useful as transitive `const`.
Interesting that there's nothing on there about C interop (likely reflecting the use cases of the author). D does it all: ImportC (compile C code), BetterC (make a D library part of a C program), and easy C interop in both directions.
Dlang always makes me think of two things: Walter Bright, resident of Hacker News. And awesome games I played on Linux in the 2000s: https://en.wikipedia.org/wiki/ABA_Games
Or you can assume it's implemented sanely. Yes if someone overrides $ and does something wildly wrong with it that'll get confusing. That's the fault of whoever wrote that code though, not of $ itself.
You can also overload +, imagine the mayhem if someone did something weird with that. should we ban overloading operators altogether?
> Syntax - UFCS (Uniform Function Call Syntax)
UFCS is such an underrated language feature. When you have UFCS you can toss out 90% of the uses of methods in favor of just plain ole functions. Add generic functions and concepts and you rarely end up needing OO support.
Reminds me of vim script's implicit method syntax [1]
Eg. any function call can be converted to a method call on the function's first parameter:
Helps a lot with chaining.Dot syntax tends to work better for code completion, though.
In addition, without uniform call syntax, adding a new method can only break subclasses, whereas with uniform call syntax it can break other client code.
There's nothing preventing UFCS from working with code completion. e.g. given:
You can now write code like this: There is absolutely no reason that typing "foo." would not suggest "addOne" as possibility.My comment was about the reverse, using function syntax for methods.
Furthermore, I don’t think it necessarily makes sense for all functions that happen to take, say, a string as their first argument, to be listed in the code completion for method invocation on a string variable.
If you merely want to define auxiliary methods outside of a class, which is the thing the GP seems to like, that’s what’s usually called “extension methods”. It doesn’t require uniform call syntax.
> using function syntax for methods
hmm, yeah fair enough I suppose. I don't think I've found a good use-case for that yet. I guess having the symmetry there makes the feature easier to explain at least? I dunno.
> Furthermore, I don’t think it necessarily makes sense for all functions that happen to take, say, a string as their first argument, to be listed in the code completion for method invocation on a string variable.
All functions in scope that happen to take a string as their first argument. If this turns into an actual problem in practice it's quite doable to refactor things such that it's not an issue.
I find that when I use autocomplete I'll be typing the first bit of the method name in any case. I never "browse" autocomplete to look for the thing I need.
Extension methods are another way to do the same thing yes, but that feels like special-casing behavior, where UFCS is more general. With extension methods you need to think of how to implement it wrt things that don't usually have methods attached. With UFCS that just works the way you'd expect it to.
OO support is more than just record.method(), which is something you don't even have in OOP systems based in multi-dispatch.
I always find it funny how people focus on the .method() syntax. You have the "pipe" operator in some functional languages that achieves the same thing, pass the result of an expression as the first argument of the chained call on the right. Nothing to do with OOP.
I think it needs a higher level experience across procedural, logical, functional and object based languages, the unique ways each one applies ideas into their programming model, how that interacts to CS, for finally understanding how those concepts come together, instead of being so fixated in language syntax.
I was about to say "Yeah, its my favourite feature of Nim!" and then I realised what account I was replying to ;)
If all you're doing is accessing public members, sure.
`private` is only private to the module, not the struct/class, (In other words, all functions in the same module are all C++ style `friend`s) and so free function in same module work.
It works for all functions that have parameters, `f(a)` and `a.f()` are equivalent.
Yes, but I presume that f() cannot access private members of a.
That's correct.
It's a mystery why D isn't far more popular than it is. Fast compilation, familiar syntax, and supports a wider range of programming paradigms than most (any?) other language.
> It's a mystery why D isn't far more popular than it is.
There's no mystery. It's a jack of all trades, master of none. E.g., the virality of the GC makes it a non-starter for its primary audience (C/C++ developers). The need to compile and the extra verbosity makes it a bad substitute for scripting like Python. Etc.
Basically, name any large niche you'd expect it to fill and you'll probably find there's a tool already better suited for that niche.
With D you don't have to use multiple languages, such as mixing Python with C. D is The One Language to Rule Them All.
(D's ability to use C code is to make use of existing C code. There's not much point to writing C code to use with D.)
> D is The One Language to Rule Them All.
That's kind of why I said it's the "jack of all trades". It's not a bad language, it just doesn't beat any existing languages I know of in their own niches (hence "master of none"), so few people are going to find it useful to drop any existing languages and use D in lieu of them.
> the virality of the GC makes it a non-starter for its primary audience (C/C++ developers)
No. If you were to say you need the GC to use all features of the language and standard library, of course, the GC does important things, but to claim a C developer wouldn't be comfortable with it because of the GC is nonsense. Just don't allocate with the GC and use the same mechanisms you'd use with C (and then build on top of them with things like @safe, reference counting, and unique pointers).
>> the virality of the GC
> Just don't allocate with the GC
"virality" is not just a word you can ignore.
I don't understand. If you're a C programmer and want to avoid the GC, there's nothing to be viral.
> If you're a C programmer and want to avoid the GC, there's nothing to be viral.
What you're suggesting is the moral equivalent of "it's easy to avoid diseases, just avoid contact with those infected", or "it's easy to avoid allergens, just avoid foods you're allergic to", or "it's easy to avoid contamination, just set up a cleanroom", or "it's easy to write deterministic code, just avoid randomness", etc.
Yes, there are things that are easy to achieve in the vacuum of outer space, but that's not where most people are interested in living.
According to them[0], D as better C is indeed just C with less footguns and some additional optional features like RAII (that one can use or not) or more comptime assumptions (again, that one can use or ignore).
I don't think what hinders their adoption is their direction, everything they say they accomplished/plan to accomplish is ideal IMO.
[0] : https://dlang.org/spec/betterc.html#retained
> D as better C is indeed just C with less footguns and some additional features like RAII (that one can use or not) or more comptime assumptions (again, that one can use, or ignore)
Having strictly more features (if we even assume that, which I don't think is accurate) does not imply better.
Javascript is just JSON with more features too. Is it a mystery that people don't ship Javascript code everywhere instead of JSON?
Completely agree after trying it. Anything that may throw an Exception requires GC. There goes 80% of D code you could use. The rest becomes inaccessible for other, similar reasons quite often. Try writing D with @nogc and it takes 10 minutes to understand that. They want to make the situation better but there’s just not enough people to tackle the huge amount of work that requires (I.e. changing most of the stdlib)
> Try writing D with @nogc and it takes 10 minutes to understand that.
Thank you. Yes, exactly. The problems aren't even subtle; it's impossible to miss them if you actually try. I don't recall even finding a reasonable way to concatenate or split strings on the heap and return them without a GC, let alone anything more complicated. It boggles my mind that people repeat the talking point that it's somehow practical to program with @nogc when the most basic operations are so painful. Nobody is going to drool at the idea of spending days/weeks of their lives reinventing nonstandard second-class-citizen counterparts to basic types like strings just to use a new language.
> They want to make the situation better but there’s just not enough people to tackle the huge amount of work that requires (I.e. changing most of the stdlib)
I don't agree that it's lack of manpower that's the problem -- at least, not yet. I think it's primarily the unwillingness to even admit this is a problem (an existential problem for the language, I think) and confront it instead of denying the reality, and secondarily the inertia and ecosystem around the existing language outside the standard library. It's not like the problem is subtle (like you said, a few minutes of coding makes it painfully obvious) or novel. The language has been out there for over a decade and a half, and people have been asking for no-GC version nearly that long. Yet, at least to the extent I've had the energy to follow it, the response has always been the canned you-can-totally-program-D-without-a-GC denials you see repeated for the millionth time here, or (at best) silence. If this sentiment has changed and I'm unaware of it, that's already significant progress.
Maybe the unwillingness to confront reality is due to the lack of manpower and how daunting the problem looks; I'm not sure. But it seems as bright as daylight that D is not going to be successful without solving this problem.
I use:
https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba...
It's pretty minimalist on purpose. I don't much care for kitchen sink types.
The BetterC is the no-gc version. Use the -betterC switch on the compiler.
Or, if you want a string result,
I tend to not use this sort of function because it doesn't manage its own memory. I use barray instead because it manages its memory using RAII. D provides enormous flexibility in managing memory. Or, you can just leave it to the gc to do it for you.> I use: https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba... It's pretty minimalist on purpose. I don't much care for kitchen sink types.
I feel you're demonstrating exactly the problems I highlighted through your example here -- including the very lack of acknowledgment of the overall problem.
Part of the problem it's trying to do too many things in too many fronts. They try to implement a borrow-checker a la Rust. But feels very poorly compared against the Rust version. It haves a "optional" GC, but it's a subpar GC. And lacks a way to use alternative GCs.
And funny, C++ has been copying many features that DLang have for many time ago. Beginning with the type inference (ie using "auto" to declare vars). And now, contractual programing and static reflection.
I really loved the language, but it's very sad that never manages to take off and become more popular and well maintained.
If I'm just coding C except a new syntax, why wouldn't I just stick with C?
It's not just new syntax, you get other nice features too.
When you use many languages, don't you become a jack of all trades, master of none? Also it's not obvious python is good for scripting, bash is better at short scripts, statically typed languages are better at long scripts, and you can't use it in CI yaml scripts. Python is more famous for data science and AI, not for scripting.
Python is useful for scripts that go beyond strings. For instance: making a series of API calls, parsing/processing/mutating.
That's not data science or AI; "more famous" -- ridiculous distinction.
Fun fact: Ansible is orchestrated Python. Half your Linux distribution of choice is a pile of Python scripts. It's everywhere.
I used "scripting" loosely (Python source files are frequently called Python scripts), I wasn't referring to shell scripts specifically. Feel free to pretend I said Bash or data science or whatever you want.
Re: your first sentence: I neither understand the logic nor do I understand how insulting the developer is going to help D succeed here even if the logic was sound.
it should at the very least fit the same niche golang does
I almost mentioned Go as another example of what it doesn't substitute for, but decided to cut off the list. But no, I don't think it fits that niche either. Go has a lot of features D doesn't have. Just off the top of my head: it's very simple, it doesn't have pointer arithmetic (yes, that's a feature here), it performs escape analysis to automatically extend the lifetime of objects that escape the stack frame, etc.
D does not allow pointer arithmetic in code marked @safe.
D does escape analysis from an alternative direction. If a pointer is qualified with `scope`, the compiler guarantees it does not escape the stack frame.
I'm well aware of those, and they obviously don't fill the gaps here.
Well that shouldn't be a mystery. Golang was marketed by Google.
We don't have a marketing budget, although we have many hard core users!
Is that really it? Why cannot you get a marketing budget, sponsored perhaps?
BTW:
I am not fond of stuff like:
Are there any ways to do this that do not involve a bunch of "."s? I do not understand "map!" and "a.idup" either, FWIW.I really want to like D, but it tries to do too many things all at once, in my opinion.
Perhaps I will give C3 a fair try.
That code is an example of f(a) being equivalent to a.f(). You can do it the f(a) way if you prefer.
`map` is an operation on a data structure that replaces one element with another, in this case `a` gets replaces with `idup(a)`. The `idup` makes a copy of its argument in memory, and marks the data is immutable.
> That code is an example of f(a) being equivalent to a.f(). You can do it the f(a) way if you prefer.
How would it look like with this particular code? Just for comparison.
> The `idup` makes a copy of its argument in memory, and marks the data is immutable.
How is one supposed to know this? Reading the documentation? I really want to look at the code and be able to know straight away what it does, or have a rough idea.
See https://en.m.wikipedia.org/wiki/Uniform_function_call_syntax - the examples section has two side by side. The Nim language version is also pretty readable even if you're not familiar with it.
As for idup... The first several search results for "dlang idup" are all useful.
> I really want to look at the code and be able to know straight away what it does, or have a rough idea.
I presume you really don't like perl, ML based (ocaml, f sharp, rust) Haskell or K.
So it's basically a pipe or like the pipe operator in some languages? Looks interesting.
It has a fairly direct analogy in some languages. A C♯ programmer reading the above will immediately recognize the analogy to .Select(), .ToArray(), and so forth from LiNQ.
* https://learn.microsoft.com/en-gb/dotnet/api/system.linq.enu...
* https://learn.microsoft.com/en-gb/dotnet/api/system.linq.enu...
Yup, it looks and behaves very much like Unix piping.
You don’t like instance functions?
Mostly wrong place at the wrong time, I guess :(
Plus the chicken and the egg problem. This is mostly from the AerynOS experience : it seems like if you want to write some moderately complicated code then you're becoming the upstream of many libraries. Especially now with Rust's popularity and ecosystem maturity on the rise, it's super hard to convince people (e.g. your boss) that you'd be better of with D compared to e.g. Rust.
Where D shines is how readable D code is compared to other languages.
I spent quite a few year on/with it back in the day. There was D1 which was like a better C, and then there was D2 which was like a better C++. Personally I preferred where D1 was going (and Tango instead of Phobos) but even with D2 it really made the day compared to what was out there and to this day still to an extent is. The thing that killed it for me, and I know at least a couple of friends as well (outside of internal politics at a time) was what kills pretty much all exotics once you start using it. Lack of (up-to-date) libraries / bindings and tooling. At the end of the day that's what you do use for most of the work you're doing anyways - libraries. So suddenly you're doing all these bindings and battling tools instead of working on actual problem at hand. This gets tiresome real quick.
For some reason, and mostly that being Mozilla, Rust got quite an initial kick to overcome that initial hurdle in haste. We're not going to mention a lot of those libs are stale in Rust world, but at least they're there and that kind of gives you momentum to go forward. Whatever you're trying to do, there's a non-zero chance there's a library or something out there for you in Rust.. and we got there real quick which then encouraged people to proceed.
That's just like my opinion, man.. but I think a key part is that first lib bindings hurdle which Rust somehow went over real quick for a critical mass of it; D hasn't.
Love the D though lol, and Walter is a 10000x programmer if you ever saw one but it might be time to hang the hat. I can only imagine how a community like Rust or I don't know Zig of those up-and-coming would benefit from his help and insights. He'd probably single-handedly make rust compile 100x faster. One can hope.
Stressing the point, Rust ship very early with formatter, linter, cargo, rustup, and was not that behind in terms of editor support.
That is basically table stakes for a new language now.
I've heard nice things about Zig being a more ergo alternative to Rust, but I haven't seen anyone compare it to D yet. From my brief testing, it seemed like Zig wasn't as ergo as D, but in theory it could evolve to maybe get there. From the outside, it doesn't seem like Zig has made any super major ergo improvements in the last year, but I could be wrong.
I don't know if I would describe Zig as ergonomic per se. It has some nice features, but the main focus is on completely explicit control over low-level details. In its design Zig always chooses explicitness and programmer control over ergonomics. If a language feature requires a lot of compiler magic then it's probably never going to be added.
I spent time back in the day with D as well, incidentally. I wonder if we crossed paths back then.
for sure we did, Steve! Sometimes multiple times a day even, hah. Check out @keyframe2 on bsky or @keyframe on the evil platform and let's reconnect.
Ha, that was so long ago I can barely remember a lot of it. I’ll give you a follow!
I'd say that the compiler not being open source during the period when it might otherwise have become popular is probably a pretty big factor.
The D parts of the compiler were released under the GPL from almost the beginning, since 2002. By 2004, a full open source compiler - what we now call gdc, officially part of gcc - was released using this GPL code. D was pretty popular in these years.
Articles like this I guess. If these are the 'lovable' features I'd hate to see the 'meh' features.
Automatic constructors - You only have to write the 'make me a box of two apples' code and not 'this is how two apples go into a box'! This is as revolutionary as 'automatic function calls', where you don't have to manually push the instruction pointer to pop it back off later.
Parenthesis omission!
If I were to parody this I'd talk about how good Scala is - in addition to classes, you can also declare objects, saving you the effort of typing out the static keyword on each member.
Sell me something nice! Millions of threads on a node. Structured concurrency. Hygienic macros. Homoiconicity. Higher-kinded types. Decent type inference. Pure functions. Transactions.
D has pure functions:
https://dlang.org/spec/function.html#pure-functions
D's pure functions are quite strict. It can be a challenge to write a function that passes strict purity guarantees - but the result is worth it!
scala is probably the poster child for supporting every paradigm you might want to use :) oz/mozart has more but that was essentially a research/teaching language specifically designed to use a wide range of paradigms in order to demonstrate them.
IIRC it was weird and not that great in D1 era, and stalled for a very long time. There were also two competing incompatible runtimes.
Then came the D2 re-write which broke backwards compatibility and took another few years.
In the meantime everyone moved on
Lack of large “sponsors”.
The "invariants" thing is fantastic, I haven't seen anything like that before and it's great. The C++26 contract stuff is fine, but this seems like a really great way of ensuring type invariants, I think I'd use this way more if it was in C++.
See Design By Contract, and the language that brought its ideas into mainstream, Eiffel.
What D or C++26 can do, is a subset of Eiffel capabilities, or more modern approaches like theorem proving in tools like Ada/SPARK, Dafny, FStar,...
Beat me to it. Eiffel et al have been at the party for a while now.
The issue is most developers do not bother to write any, and the ones that are written are most-often vapid typing failures ("these`int`s cannot be negative" should be handled by a type). I studied this field in grad school, and the entire problem almost always devolves into convincing developers to engage with the system.
I find that is the case with almost all methodologies for software quality improvement. If you can't enforce that people follow it then it's not worth anything.
It only takes one enlightened CTO :)
I feel like this feature could be implemented on top of more universal features.
Checking input parameters is easy, just write asserts at the start of the function.
Checking result requires "destructor" block and some kind of accessible result variable, so you can write asserts in this destructor block which you can place at the start of the function, as well.
Checking class invariants requires a way to specify that some function should be called at the end of every public function. I think, it's called aspect-oriented programming in Java and it's actually useful for more things, than just invariant checking. Declarative transaction management, logging.
There are probably two schools of programming language designs. Some put a lot of features into language and other trying to put a minimal number of features into language which are enough to express other features.
Having the higher level abstraction built into the language gives extra semantic meaning that can be taken advantage of to build tooling. For example, one could build a symbolic model checker based on the contract specifications. It would be possible to do the same with the lower level features, but a lot harder if they aren't used consistently and correctly.
Same reason function calls are better than arbitrary jumps.
Just a personal anecdote, Walter Bright's Digital Mars C++ compiler also had the contracts (D started life almost literally as recycled code from Mr. Bright's other compilers - he wrote a native Java compiler, a Javascript 1.3 stdlib, and a C++ compiler with a bunch of extensions.... smash those together and you have the early D releases!).
Anyway, I used the DM C++ compiler originally because it was the only one I could download to the high school computers without filling out a form, and pimply-face youth me saw "DESIGN BY CONTRACT" at the top of the website and got kinda excited thinking it was a way to make some easy money coding online.
Imagine my disappointment when I saw it was just in/out/invariant/assert features. (I'm pretty sure D had just come out when I saw that, but I saw `import` instead of `#include` and dismissed it as a weenie language. Came back a couple years later and cursed my younger self for being a fool! lol)
The in/out features come into their own when inheritance is in play, i.e. for member functions of classes and interfaces. See https://dlang.org/spec/function.html#in_out_inheritance
`import` is so cool we extended it to be able to import .c files! The D compiler internally translates them to D so they can be used. When this was initially proposed, the reaction was "what's that good for?" It turned out to be incredibly useful and a huge time saver.
The concept is sort of like C++ being a superset of C and so being able to incorporate C code, except unlike C++, the C syntax can be left behind. After all, don't we get tired of:
> struct Tag { ... } Tag;
What's the thing with the syntax? If you don't intend to use the type elsewhere don't give it a tag, if you want, you have to give it a name. (Assuming you are annoyed by the duplicate Tag)
My C++ compiler also implemented contracts back in the 90s: https://www.digitalmars.com/ctg/contract.html
Modern C++ is slowly adopting D features, many of which came from extensions I added to my C++ compiler.
I think they were introduced with Eiffel, which was all about design by contract
Ada has that too, for what it is worth: https://learn.adacore.com/courses/intro-to-ada/chapters/cont... and https://en.wikibooks.org/wiki/Ada_Programming/Contract_Based.... It can be verified at compile time.
But what I love the most is: https://news.ycombinator.com/item?id=43936007
Instead of:
You can do this, in Ada: or alternatively: and then you can use attributes such as: Does D have anything like this? Or do any other languages?Yeah, these look excellent. Am curious if D's invariants can be traced back to Ada/Spark at all (I don't know much about Ada except that it has these sorts of safety features).
Maybe this might help: https://news.ycombinator.com/item?id=44449835
>The "invariants" thing is fantastic, I haven't seen anything like that before and it's great.
is it not the same as the one in Eiffel?
> Invariants are functions that run at the start and end of every public member function
these are just runtime assertions
EDIT: how am i getting downvoted for copy-pasting literally what the article verifies?
Yes, but they are guaranteed to run at the beginning and end. C/C++ asserts need to handle any return path, whereas D has functionality to mark statements to run at the end of any return path while only being written once.
See also the scope(exit) feature.
You can accomplish the same exact thing with
https://en.cppreference.com/w/cpp/experimental/scope_exit.ht...
The idea for scope-exit came from Andrei Alexandrescu. See https://dlang.org/articles/exception-safe.html
He demonstrated it with C++ templates, but the D one is far more straightforward.
I think there's something to be said about them running automatically that is lost when you say they're just asserts.
i don't get it - if do
do you think those asserts don't "run automatically"?You define the invariants once for the class and they are run around every public function. Done manually you'd probably use a helper object that calls the invariants in its constructor and destructor (have to handle exceptions) that you have to add to every public function's definition.
Maybe it's the editorial "just"?
Like: software programs can't be that difficult to create properly because they are just 1s and 0s.
This is not the first time someone getting down-voted for using the word "just". I do not know if this really is warranted, however.
Has anyone compared D and Zig? I originally learned D over one weekend and then went on to completed several code competitions- the ergonomics of D are just fantastic.
I like D, it's fascinating and powerful language. It made it even more curious when I watched Tsodings video on D. One thing that came to my mind when reading the article is that things like int.init instead of 0 and $ as shorthand for array.length does add to the mental load.
One good memory I had is a couple of years ago when I built a little forum using D. Man the site was blazing fast, like the interaction was instant. Good times.
The `.init` is there because the default initializer isn't always 0.
In my opinion, some features of D are good, but I do not like all of them.
CTFE is good.
I do not really like the UFCS; if you want it to be used like a member of the first parameter then you should define it as a member of the first parameter (possibly as a inline function that only calls the freestanding function with the same name, if that is what you want it to do). (You could use a macro to do this automatically if you want to.)
Scoped imports is good, but I think that scoped macros would also be helpful.
Exhaustive switch seem like it might be better if designed differently than it is, but the idea seems to be not bad, in general.
Honestly the main reason UFCS is good is when you don't "own" the type your function would need to be a member of. Most common one I run into is the "to" function from the stdlib. You can do this:
But now lets say you want to convert ints to MyCustomInts? You can hardly attach a new "to" member to int, but with UFCS it's easy. Just declare a to function anywhere in scope: and it'll magically work:UFCS is a bit overreaching but I think it's great for its intended use of chaining expressions on ranges and such.
that actually wasn't its intended use; that's a side effect. The original intended use came from Effective C++ by Scott Meyers: "Prefer non-member non-friend functions to member functions.". It was meant to make that as syntactically appealing as the members.
Invariants, dependent typing, and refinement types FTW.
In Rust land, it really need integration of something like flux into the language or as a gradually-compatible layer.
Can't have safe software without invariant checking, and not just stopping at bounds checking.
When I last looked at D they did a lot of interesting stuff with compile time function execution and s.th. like dependent types that they hacked into the language.
Can someone give me an update about the current state of this?
I really enjoy these lists of interesting features from various languages. They pop up occasionally on HN but now I can’t find them (Hillel Wayne had multiple).
I want a meta list of all these interesting features across languages.
EDIT: I found one! “Micro features I’d like to see in more languages” https://buttondown.com/hillelwayne/archive/microfeatures-id-...
I don't use D but think that error handling is one major feature:
https://dlang.org/articles/exception-safe.html
In concrete, looks to me to be the only language that covers the major ways to do it.
(In concrete the `scope` way is the one I found inspiring. . I think the exceptions can go and be replace by it for langs where exceptions are removed)
`scope` is very good at its job. It guarantees that a pointer passed as an argument does not escape the caller's scope. I find it almost as useful as transitive `const`.
D is one of the most beautiful and very efficient language. I wonder why it never got the attention it deserves.
All wonderful features. Design by Contract in particular is massively underused in mainstream languages.
Interesting that there's nothing on there about C interop (likely reflecting the use cases of the author). D does it all: ImportC (compile C code), BetterC (make a D library part of a C program), and easy C interop in both directions.
Dlang always makes me think of two things: Walter Bright, resident of Hacker News. And awesome games I played on Linux in the 2000s: https://en.wikipedia.org/wiki/ABA_Games
$ operator. great idea! i believe the only contender is julia (with "end" but that is a SUUPER awkward keyword since end also closes functions)
> Structs and classes can even overload this operator
nope. fuck, now it's a terrible idea
> i believe the only contender is julia
C# has "^n" notation that means "length - n". For example:
Take the last element of an array or any other structure that supports indices:
Take the last three elements of an array or any other data structure that supports ranges:I don't see why it would be a terrible idea. It's pretty convenient if you're slicing a multidimensional array.
except are you sure that's what it's doing? it could have been overloaded. you have to ask yourself that every time.
Or you can assume it's implemented sanely. Yes if someone overrides $ and does something wildly wrong with it that'll get confusing. That's the fault of whoever wrote that code though, not of $ itself.
You can also overload +, imagine the mayhem if someone did something weird with that. should we ban overloading operators altogether?