June 18, 2014
On Wednesday, 18 June 2014 at 20:00:20 UTC, H. S. Teoh via Digitalmars-d wrote:
> Until you compile with -release, and then suddenly invalid input crashes
> your program. :-P (Then you'll go and fire the guy who wrote it.)
>
>
> T

My point exactly. If contracts allow things like what Bearophile wants to work, then people might use them to do input/output validation instead of validating basic assumptions, like they're supposed to do. Then we have problems like you described. Maybe it's a good idea to keep contracts as-is and not allow them to change a function's semantics like this, since they are removed in release.
June 18, 2014
On Wednesday, 18 June 2014 at 13:31:01 UTC, bearophile wrote:

> OK. But how?

The simplest example is probably the constructor for Bound defined something like

this(T value)
{
    enum r = __traits(valueRange, value);
    static if (this.min <= r[0] &&
               this.max <= r[1])
    {
        // were within inclusive range
    }
    else
    {
        // need range check here!
    }
    _value = value;
}

assuming that

- `this.min` and `this.max` are statically defined values (which they are in bound.d) and that
- value range information is propagated to the constructor argument `value`

Other functions such as arithmetic operators should be analogous.

Destroy!
June 18, 2014
> Destroy!

Correction:

static if (this.min <= r[0] &&
                       r[1] <= this.max)
June 18, 2014
On Wednesday, 18 June 2014 at 17:00:36 UTC, bearophile wrote:
> And slowly D Contract Programming starts to become a grown-up language feature.

Interesting!
June 18, 2014
Meta:

> My point exactly. If contracts allow things like what Bearophile wants to work, then people might use them to do input/output validation instead of validating basic assumptions, like they're supposed to do. Then we have problems like you described. Maybe it's a good idea to keep contracts as-is and not allow them to change a function's semantics like this, since they are removed in release.

This is an interesting topic.

D contracts are a potentially important language feature that is currently underused, perhaps because it's currently a little more than a nicer place to locate asserts (and it still lacks the pre-state ("old") feature).

Function contracts don't change the program semantics, they define part if it at a higher level than the function body itself.

When your function has a post-condition that specifies the function to return a value between 0 and 9 inclusive, your function is defined to return such range, that's its semantics. If the function returns something outside that range, then your function has a bug. Relying on the function semantics to allow casts is one basic usefulness of contracts, but this is just a starting point (if take a look at the Whiley language you see what I mean: whiley.org ).

If you compile your code with -release you are assuming your code doesn't have bugs, and your contracts are always satisfied.

Perhaps D programmers need to stop compiling with -release. Perhaps even the name of this compiler switch should be changed to something more descriptive of its unsafety and meaning.

The presence of -release compiler switch can't hold back the use of contract programming for all it's worth. Currently D is not taking its contract programming seriously :-)

Bye,
bearophile
June 18, 2014
Nordlöw:

> The simplest example is probably the constructor for Bound defined something like

Very good, with this you have framed the discussion well :-)


> - value range information is propagated to the constructor argument `value`

The problem is, I think this is currently false (even if you call your constructor with just a number literal). I'd like this feature in D, but I don't know how much work it needs to be implemented.

D language is designed to allow you to create data structures in library code able to act a lot like built-in features (so there's a refined operator overloading, opCall, static opCall, and even more unusual things like opDispatch), but there are built-in features that can't yet be reproduced in library code, observe:


void main() {
    int[5] arr;
    auto x = arr[7];
}


dmd 2.066alpha gives a compile error:

test.d(3,14): Error: array index 7 is out of bounds arr[0 .. 5]


In D there is opIndex to overload the [ ], but its arguments are run-time values, so I think currently they have no way to give a compile-time error if you use an index that is known statically to be outside the bounds. So currently you can't reproduce that behavour with library code. Propagating the value range information to the constructor, plus the new __trait(valueRange, exp), allow to solve this problem. And indeed this allows to implement nice ranged values like in Ada, and to do what the Static_Predicate of Ada does.


Another common example of what you currently can't do with library code:

void main() {
    import std.bigint;
    BigInt[] arr = [10, 20];

    import std.numeric;
    alias I10 = Bound!(int, 0, 10);
    I10[] arr = [8, 6, 20];
}


(In theory the compiler should also catch at compile time that bug, because 20 is outside the valid bounds of I10.)


The "enum precondition" I've suggested elsewhere is a generalization of that feature (but it's not a strict subset because it only works with literals, so it's good to have both features), because it manages not just ranges of a single value, but also other literals, like an array:


void foo(int[] arr)
enum in {
    assert(arr.all!(x => x < 10));
} in {
    assert(arr.all!(x => x < 10));
} body {
    // ...
}
void main() {
    foo([10, 20]); // gives compile-time error.
}


This is possible only if the source code of foo is available in the compilation unit. In presence of separated compilation the enum precondition is ignored. So the enum preconditon can't replace regular pre-conditions, they are useful to catch statically only a subset of bugs. The same happens with the idea of propagating range information to the constructors. One way to avoid or mitigate this problem is to leave available the source code of functions with an enum pre-conditions, just like with templates. Perhaps this is not a big problem because enum preconditions and constructor value range propagation are meant to be used mostly in library code, like in Bound integers, etc.

Bye,
bearophile
June 19, 2014
On Wed, Jun 18, 2014 at 10:08:06PM +0000, bearophile via Digitalmars-d wrote: [...]
> D language is designed to allow you to create data structures in library code able to act a lot like built-in features (so there's a refined operator overloading, opCall, static opCall, and even more unusual things like opDispatch),

opDispatch is extremely powerful; I think we've only barely begun to tap into what it can do. Like my recent safe-dereference template function. ;-)


> but there are built-in features that can't yet be reproduced in library code, observe:
> 
> void main() {
>     int[5] arr;
>     auto x = arr[7];
> }
> 
> 
> dmd 2.066alpha gives a compile error:
> 
> test.d(3,14): Error: array index 7 is out of bounds arr[0 .. 5]
> 
> 
> In D there is opIndex to overload the [ ], but its arguments are run-time values, so I think currently they have no way to give a compile-time error if you use an index that is known statically to be outside the bounds. So currently you can't reproduce that behavour with library code. Propagating the value range information to the constructor, plus the new __trait(valueRange, exp), allow to solve this problem. And indeed this allows to implement nice ranged values like in Ada, and to do what the Static_Predicate of Ada does.

It seems like ultimately, we want to have some kind of uniformity between built-in compiler intrinsics and user-defined types, such as allowing user-defined types to access internal compiler knowledge like value range and many other things.

One very useful thing to have would be a way to tell if a particular symbol has a compile-time known value. You could then do things like custom constant-folding on user-defined types by automatically simplifying expressions at compile-time.

Hmm. Maybe this is already possible to some extent?

	template hasCompileTimeValue(alias Sym) {
		alias hasCompileTimeValue = __traits(compiles, (){
			enum val = Sym;
		});
	}
	int x = 10, y;

	// Will this work? (I don't know)
	assert(hasCompileTimeValue!x);
	assert(!hasCompileTimeValue!y);


> Another common example of what you currently can't do with library code:
> 
> void main() {
>     import std.bigint;
>     BigInt[] arr = [10, 20];
> 
>     import std.numeric;
>     alias I10 = Bound!(int, 0, 10);
>     I10[] arr = [8, 6, 20];
> }
> 
> 
> (In theory the compiler should also catch at compile time that bug, because 20 is outside the valid bounds of I10.)
[...]

This is a different instance of the same problem as above, isn't it? If Bound has access to compiler knowledge about value ranges, then it would be able to statically reject out-of-range values.


T

-- 
"I'm not childish; I'm just in touch with the child within!" - RL
June 19, 2014
On Wednesday, 18 June 2014 at 14:10:11 UTC, Peter Alexander wrote:
> On Wednesday, 18 June 2014 at 06:40:21 UTC, Lionello Lunesu wrote:
>> I got this thing working and I think it's about time I get some comments on it.
>
> This is really cool. Good job!
>
> One thing we need to be careful with is how this is specified.
> Because of all the compile time introspection (e.g. __traits
> compiles and now __traits intrange), this VRP needs to be
> precisely specified otherwise you end up with incompatible
> differences between different compiler front ends. I don't want
> to see code compiling in one compiler, but not another like we do
> in C++ (and if we do, I'd like it to be minimized).
>
> If this goes in, the mechanisms by which is works need to be
> added to the spec.

I agree with what is stated here.

And will repeat that this sounds awesome.
June 19, 2014
On 06/18/2014 09:54 PM, Meta wrote:
> ...
>
> This could be a bad thing. It makes it pretty enticing to use contracts
> as input verification instead of logic verification.

The following is doable as well with a standard range analysis:

byte foo(immutable int x){
    if(x<byte.min || x>byte.max)
        throw new InvalidArgumentException("...");
    return x; // ok
}
June 19, 2014
> void main() {
>     import std.bigint;
>     BigInt[] arr = [10, 20];
>
>     import std.numeric;
>     alias I10 = Bound!(int, 0, 10);
>     I10[] arr = [8, 6, 20];
> }

In the meantime Kenji delivers, those BigInt array literals are possible with this patch:
https://github.com/D-Programming-Language/dmd/pull/3680

Bye,
bearophile