Remix.run Logo
OskarS 2 days ago

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++.

pjmlp a day ago | parent | next [-]

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,...

scrubs a day ago | parent [-]

Beat me to it. Eiffel et al have been at the party for a while now.

discardable_dan 2 days ago | parent | prev | next [-]

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.

wavemode 2 days ago | parent | next [-]

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.

esafak 2 days ago | parent | prev [-]

It only takes one enlightened CTO :)

destructionator 2 days ago | parent | prev | next [-]

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)

WalterBright 2 days ago | parent | next [-]

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;

?
1718627440 a day ago | parent [-]

> 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)

WalterBright 42 minutes ago | parent [-]

Which would you prefer:

    struct Tag { ... }
or:

    typedef struct Tag { ... } Tag;
? It's just simpler and easier to write code in D than in C/C++. For another example, in C/C++:

    int foo();
    int bar() { return foo(); }
    int foo() { return 3; }
The D equivalent:

    int bar() { return foo(); }
    int foo() { return 3; }
WalterBright 2 days ago | parent | prev [-]

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.

vbezhenar a day ago | parent | prev | next [-]

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.

sirwhinesalot a day ago | parent [-]

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.

a day ago | parent [-]
[deleted]
peterashford 2 days ago | parent | prev | next [-]

I think they were introduced with Eiffel, which was all about design by contract

johnisgood 2 days ago | parent [-]

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:

  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;
or alternatively:

  type U32 is mod 2 ** 32;
and then you can use attributes such as:

  First  : constant U32 := U32'First; -- = 0
  Last   : constant U32 := U32'Last;  -- = 2 ** 32 - 1
  Range_ : constant U32 := U32'Range; -- Range 0 .. 2**32 - 1
Does D have anything like this? Or do any other languages?
12_throw_away 2 days ago | parent | prev | next [-]

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).

johnisgood 2 days ago | parent [-]

Maybe this might help: https://news.ycombinator.com/item?id=44449835

2 days ago | parent | prev | next [-]
[deleted]
fuzztester 2 days ago | parent | prev | next [-]

>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?

almostgotcaught 2 days ago | parent | prev [-]

> 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?

LorenDB 2 days ago | parent | next [-]

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.

almostgotcaught 2 days ago | parent [-]

You can accomplish the same exact thing with

https://en.cppreference.com/w/cpp/experimental/scope_exit.ht...

WalterBright a day ago | parent [-]

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.

jayd16 2 days ago | parent | prev | next [-]

I think there's something to be said about them running automatically that is lost when you say they're just asserts.

almostgotcaught 2 days ago | parent [-]

i don't get it - if do

  int foo(int a) {
    assert(a > 5);
    int b = a * 10;
    assert(b > 50);
    return b;
  }
do you think those asserts don't "run automatically"?
gblargg 2 days ago | parent [-]

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.

readthenotes1 2 days ago | parent | prev [-]

Maybe it's the editorial "just"?

Like: software programs can't be that difficult to create properly because they are just 1s and 0s.

johnisgood 2 days ago | parent [-]

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.