December 01, 2018
On Saturday, 1 December 2018 at 09:52:50 UTC, Kagamin wrote:
> On Saturday, 1 December 2018 at 08:22:57 UTC, Paolo Invernizzi wrote:
>>> I also like the history of immutable btw: remember when it was called "invariant" and why it changed?
>>
>> It was renamed just for clarity, when someone got confused about it, the explanation was always something like "it's simple an immutable value, like ROM"
>
> AFAIK, it was renamed for consistency with unittests, otherwise invariant blocks required braces: invariant(){ ... } or it would be ambiguous with block attribute.

Yes, invariant block used to have braces...
Or at least I think to remember a big code session devoted to "delete the invariant braces" in the codebase...

Oh well...
December 01, 2018
Also string as just a plain immutable slice of utf8 text is a distinct feature too. Nobody else did it. Swift only recently adopted utf8 encoding, I thought it always worked that way, at least NSString in gnustep did.
December 01, 2018
On 30.11.18 20:34, Andrei Alexandrescu wrote:
> Over the past few months, Walter, Mike, and myself have been working on a draft submission for the HOPL 2020 conference (History of Programming Languages).
> 
> The submission proceeds in several stages. Currently we've been through one round of preliminary review. Here is the current draft:
> 
> http://erdani.com/hopl2020-draft.pdf
> 
> We'd appreciate feedback and additional historical details.
> 
> 
> Thanks,
> 
> Andrei (on behalf of Walter and Mike as well)

Great write-up!

Comments:

110-111: UB on assertion failure is still terrible even if it is considered to be a bug and a non-recoverable error. Those things have nothing to do with each other. The airplane analogy would be roughly: "Components that are about to fail may start to arbitrarily violate the laws of physics."

169: Potentially confusing language. An associative operator op usually is taken to be an operator that satisfies (a op b) op c = a op (b op c).

175: ergnomic -> ergonomic

482: I don't think it is true that array slices are an enabler for CTFE. It is easy to write a safe and complete enough interpreter for code that uses pointer indexing.

499: "Built-in tuple". This terminology is not very forward-compatible. Typically, tuples in programming languages do not auto-expand. Also, before this point, it is not discussed what a "built-in tuple" is.

544: Boolean expresions -> Boolean expressions

753: Something is missing here. Maybe add a sentence that explains that pure has been changed from its initial definition or rewrite in another way.

December 01, 2018
On Sat, Dec 01, 2018 at 11:02:51AM +0000, Kagamin via Digitalmars-d wrote: [...]
> Ugh, purity shouldn't be a default. Purity by default results in a monadic joke language, consequences are immense as you can see by the haskell example, effectively kills scripting and system programming.

I think you misunderstand the intent here.  Purity by default doesn't mean everything must be pure with no way out except awkward periphrases like monads.  It means you opt out of purity by specifying impure, rather than start with impure and opt into purity by specifying pure. I.e., an undecorated function will be implicitly pure, but if you want impure code, you add an `impure` attribute to the function.


> Immutability by default requires shadowing to emulate mutability, so isn't without shortcomings, also kills ranges.

Again, the idea here isn't to force everything to be immutable; rather, it's to make variables immutable by default, but if you want them to be mutable, you specify them as mutable. Cf. Rust's var/val.


> Safety is still a prototype, too early to deploy it in production. Combined they destroy convenience and to an extent performance and restrict language to haskell niche, which is nothing to be proud of.

It's true that @safe isn't completely implemented yet (and IMO the implementation suffers from the inherent incompleteness of using a blacklist rather than a whitelist), but I think the idea here is again that functions would be @safe by default, and you can opt out by specifying @system when you need to.  This would mean @safe checks will apply by default to general D code, except for the few exceptions when you need to do something @system. The two can still interface via @trusted, as they do today.

Basically, the language would pretty much remain the same as it is, except with some attributes negated by default, i.e., an undecorated function would be pure @safe, but you can opt out by specifying impure / @system.  Variables would be immutable by default, but you specify mutable when they need to mutate.  This would encourage the coder to write cleaner code, but doesn't stop you from "getting your hands dirty" with low-level impure/unsafe/etc. code when you need to, e.g. to implement low-level primitives or squeeze out performance.  (Note also that by being pure/immutable by default is likely also to afford the optimizer more opportunities for optimization, so it may not be the performance hit you seem to think it is, it could be quite the opposite.)


T

-- 
You have to expect the unexpected. -- RL
December 01, 2018
On Friday, 30 November 2018 at 19:34:58 UTC, Andrei Alexandrescu wrote:
> We'd appreciate feedback and additional historical details.


I just remembered "The D Programming Language", your book! I don't think it got a mention in there either...
December 01, 2018
On Saturday, 1 December 2018 at 08:22:57 UTC, Paolo Invernizzi wrote:
> It was renamed just for clarity, when someone got confused about it, the explanation was always something like "it's simple an immutable value, like ROM"

Indeed. It was invariant to avoid making a new keyword, but since invariant() and invariant(stuff) ended up meaning something different, and people kept saying "what is an invariant variable" and the answer was "it is immutable"... they just changed it to immutable.

Now, the addition of this was not the split point of D1 and D2 (though I would point out that split was more like a version forked off and called itself D1 while the language in general kept changing like it always had - the article there called it "arbitrary" which is totally true), but it was really close to it, like a couple months later, and I consider it to be the main breaking change of that "era" (in addition to a bunch of functions being renamed in Phobos, which was really annoying but less fundamental). Very little code before and after the addition of invariant would actually still work, since the type of string changed!

(Though again, invariant/immutable was actually added *before* the type of string actually changed; there were a few releases in this process.)



The other notable thing though was how all this stuff I think is so analogous to the memory safe talk of today. Thread safe by default was a huge talking point. For a couple years, everyone was throwing out fancy proposals, tons of major stuff was added (including several big breakages), the TDPL book was published promising more like `shared` meaning something. We were told this is the future of programming - multicore is the big thing, no more serial processors. If D is going to go anywhere it MUST capture this. Comparisons with Erlang all around.

But where are we now? Those features are there... but were they the revolution? I'd say no. In fact, most people seem to simply ignore them and carry on.


And now, memory safety is trumpted as the next big thing that D MUST capture. Comparison with Rust all around. (Never mind that D has basically already solved memory safety with a well-known, widely-implemented traditional solution: conservative garbage collection.) Many features being added.

But where do you think we'll be in five years? I know my guess.
December 01, 2018
On Saturday, 1 December 2018 at 11:02:51 UTC, Kagamin wrote:
> Ugh, purity shouldn't be a default.

You'd just put "impure" on functions that need to access more.

And keep in mind too that D's pure basically means "no non-immutable globals". So if you need to access one of those, you just slap impure on it and go your merry way. So it isn't very restrictive at all and the slight hassle of saying "impure" might even lead to encouraging better design.

I'm about as traditional as D coders come, but I'd welcome that. (In fact, I'm of the opinion that these attributes are near-useless because they aren't default - there's nothing to gain for library writers to use them right now, whereas if it was swapped, you would have something to gain: your design would work.)
December 01, 2018
On Saturday, 1 December 2018 at 16:07:28 UTC, H. S. Teoh wrote:
> On Sat, Dec 01, 2018 at 11:02:51AM +0000, Kagamin via Digitalmars-d wrote: [...]
>> [...]
>
> I think you misunderstand the intent here.  Purity by default doesn't mean everything must be pure with no way out except awkward periphrases like monads.  It means you opt out of purity by specifying impure, rather than start with impure and opt into purity by specifying pure. I.e., an undecorated function will be implicitly pure, but if you want impure code, you add an `impure` attribute to the function.
>
> [...]

And maybe, class methods final by default?

:-P

P
December 01, 2018
On Sat, Dec 01, 2018 at 04:13:20PM +0000, Adam D. Ruppe via Digitalmars-d wrote: [...]
> The other notable thing though was how all this stuff I think is so analogous to the memory safe talk of today. Thread safe by default was a huge talking point. For a couple years, everyone was throwing out fancy proposals, tons of major stuff was added (including several big breakages), the TDPL book was published promising more like `shared` meaning something.  We were told this is the future of programming - multicore is the big thing, no more serial processors. If D is going to go anywhere it MUST capture this. Comparisons with Erlang all around.
> 
> But where are we now? Those features are there... but were they the revolution? I'd say no. In fact, most people seem to simply ignore them and carry on.

IMO, one of the main reasons people ignore these features and carry on as before is because of the lack of an abstraction that's easy to reason about and apply in everyday programming.

Thread-safety, with the current abstractions, is exceptionally hard to reason about, full of hidden gotchas, tricky semantics, and side-effects that "leak" through the abstractions.  People have difficulty "thinking in these terms", i.e., using these facilities in a pervasive way, because it's just so counter-intuitive. It's like being forced to speak in a foreign language. Some people may be able to throw down the gauntlet, take the bull by the horns, and learn the idiom despite of the difficulties, and become masters of it. But the majority of programmers will simply resort to "learning just enough foreign words to get by" (the bare minimum constructs needed to make threaded code work), and revert to a more comfortable idiom (traditional serial programming) for everything else.

It won't be until an abstraction is invented for threaded computation that's (1) intuitive to write without feeling like you're walking on a minefield, and (2) easy enough to reason about that you can reliably predict the consequences of any arbitrary combination of threaded constructs, that this kind of programming will be adopted by the general programmer population.

The recent "nursery" idea from a Python library that somebody posted here seemed to be a promising step in this direction. But still, it doesn't seem to be quite there yet.


> And now, memory safety is trumpted as the next big thing that D MUST capture. Comparison with Rust all around. (Never mind that D has basically already solved memory safety with a well-known, widely-implemented traditional solution: conservative garbage collection.) Many features being added.
> 
> But where do you think we'll be in five years? I know my guess.

Memory safety is much more than merely having a GC instead of doing malloc/free by hand.

There's also the pervasive design mistake of C, carried over to C++ and some other C derivatives, of conflating arrays with pointers (with no bounds), leading to buffer overruns everywhere.  Having worked with C/C++ code for more than a decade, I'd hazard a guess that your average C codebase is probably full of hidden buffer overflows that you just haven't realized are there yet, and with more looming on the horizon because it's just so easy to pass the wrong length with the pointer to your array, or, in bad cases, passing just a bare pointer with no guarantees that the function receiving the pointer won't write beyond the end of the buffer.  D solved this major hole from day 1 by making arrays always carry length around, thus freeing the programmer from the mental burden of keeping track of array sizes and passing the right sizes with the right pointers.

These two probably constitute the underlying causes of the majority of memory unsafety bugs in software. But there are others, like casting untyped memory (or memory of a different type) to/from typed memory correctly. This is partially addressed by various small but important details, like T[] converting to void[] with implicit length adjustment (implemented in druntime), APIs like std.stdio's rawRead/rawWrite that use templates to retain type information (with underlying void[] conversions that benefit from implicit length adjustment), etc.. The remainder, like returning references to local variables, have still to be fully addressed by things like the oft-maligned DIP1000.

Essentially, D has already taken care of two of the biggest causes of memory corruption, and solved a good number of the other causes. But there are still holes that remain that need to be fixed before we are 100% memory safe.

But if you're expecting a "revolution" from this, don't be surprised at disappointment, because at the end of the day, what we're doing is simply to take care of these issues "under the hood", so that your average D coder doesn't even need to think about memory safety issues and they can reap its benefits more-or-less implicitly.  It goes along with Walter's philosophy from airplane design: the intuitive way to write code will be the correct (memory-safe) way; it will be either impossible or require actual effort to write memory-unsafe code.  We want to avoid C++'s mistake, where due to legacy issues the straightforward way to write code is almost always the wrong way, and writing correct C++ code requires a lot of effort and care on the part of the programmer. D's goal is to make the straightforward way correct by default, and to make incorrect code stick out like a sore thumb. (Whether it achieved / will achieve this, of course, is a different discussion.)

So in this sense your bet is right on: in 5 years (and IMO, that's pretty optimistic) nobody will even notice memory safety because it will already be there by default, and will be (mostly) implicit, so you reap the benefits even without necessarily being aware of it. That would be the sign of our success. But OTOH if in 5 years people are still talking about memory safe issues in their D code, that's a sign that @safe has failed.


T

-- 
Живёшь только однажды.
December 01, 2018
On Sat, Dec 01, 2018 at 05:32:44PM +0000, Paolo Invernizzi via Digitalmars-d wrote:
> On Saturday, 1 December 2018 at 16:07:28 UTC, H. S. Teoh wrote:
> > On Sat, Dec 01, 2018 at 11:02:51AM +0000, Kagamin via Digitalmars-d wrote: [...]
> > > [...]
> > 
> > I think you misunderstand the intent here.  Purity by default doesn't mean everything must be pure with no way out except awkward periphrases like monads.  It means you opt out of purity by specifying impure, rather than start with impure and opt into purity by specifying pure.  I.e., an undecorated function will be implicitly pure, but if you want impure code, you add an `impure` attribute to the function.
> > 
> > [...]
> 
> And maybe, class methods final by default?
[...]

This one is a toss-up.  Based on Walter's airplane design philosophy, the idea is to make the default behaviour the correct / good one, and require effort to write incorrect / bad code.  Purity by default is good because pure code has no side-effects, which makes it easier to reason about (and in some cases, easier for the optimizer to optimize). Immutable by default is good because it provides more optimization opportunities (if something doesn't need to change, it shouldn't be mutable, then the optimizer can eliminate redundant loads where it would otherwise be unsure whether intervening code may have changed the original value, if it was mutable), and also prevents common errors like accidentally changing a value that's not expected to change, or code smells like reusing variables for something completely unrelated to the original use.

But for class methods, it's less clear what's the better default. Especially in D, where structs and compile-time template polymorphism tend to be preferred where possible, and it almost feels like the raison d'etre of classes is OO-style runtime polymorphism, so it would make sense for methods to be virtual by default. OTOH, making them final by default would also mean you don't incur runtime costs except when you explicitly ask for virtual.  But then you'd have to specify virtual everywhere in a construct that seems to be intended precisely for such a use in the first place. So I'm tempted to say virtual by default makes more sense (code correctness / clarity / less boilerplate come first, performance comes later), but I can see how it can be argued both ways.


T

-- 
Do not reason with the unreasonable; you lose by definition.