June 17, 2012
On Sunday, June 17, 2012 08:38:41 Philippe Sigaud wrote:
> On Sat, Jun 16, 2012 at 9:22 PM, bearophile <bearophileHUGS@lycos.com>
wrote:
> > I see. is that semantically different from this (beside being shorter)?
> > 
> > struct NoZero {
> >    int value;
> >    this(int x) { value = x; }
> >    alias value this;
> >    invariant() { assert(value != 0); }
> > }
> > void main() {
> >    auto a = NoZero(5);
> >    auto b = NoZero(0);
> > }
> 
> The invariant isn't invoked through alias this, it seems:
> 
> void main() {
>    auto a = NoZero(5);
>    a = 0; // compiles and runs happily
> }

It wouldn't be. No public function was called. All you did was access the public member variable. Having public members and invariants at the same time doesn't work very well.

- Jonathan M Davis
June 17, 2012
On Sun, Jun 17, 2012 at 8:42 AM, Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> It wouldn't be. No public function was called. All you did was access the public member variable. Having public members and invariants at the same time doesn't work very well.

If I change

int value;

to

private int value;

it can still be accessed through alias this (I guess) and no invariant
is called.
June 17, 2012
On Sunday, June 17, 2012 09:04:16 Philippe Sigaud wrote:
> On Sun, Jun 17, 2012 at 8:42 AM, Jonathan M Davis <jmdavisProg@gmx.com>
wrote:
> > It wouldn't be. No public function was called. All you did was access the public member variable. Having public members and invariants at the same time doesn't work very well.
> 
> If I change
> 
> int value;
> 
> to
> 
> private int value;
> 
> it can still be accessed through alias this (I guess) and no invariant
> is called.

Well, I'm not sure if it's supposed to be legal to access value like that if it's private, but public or private has nothing to do with the invariant in this  case. The invariant only runs when you call public _functions_ (it may be run with package and protected as well - I don't recall - but never with private). It's run before and after they're executed. You're accessing a member _variable_. That's why the invariant isn't run. It doesn't matter what the access level of a variable is, the invariant is never run just because you accessed it. That's why if you want to be able to guarantee your invariants, you should never have public member variables on types with an invariant. And if alias this allows you to expose a private member variable, then it's in the same boat as a public one. Invariants and public member variables just don't mix.

- Jonathan M Davis
June 17, 2012
That's a *lot* more code but I think we could do something like

struct NoZero {
    private int _value;

    invariant() { assert(_value != 0); }

    this(int x) { _value = x; }

    NoZero opBinary(string op)(const ref NoZero rhs) {
    	return NoZero(mixin("this._value"~op~"rhs._value"));
    }
    NoZero opBinary(string op)(int rhs) {
    	return NoZero(mixin("this._value"~op~"rhs"));
    }
    ref NoZero opUnary(string op)() if(op=="++"||op=="--"){
    	mixin(op~"this._value;");
    	return this;
    }
    ref NoZero opAssign(int rhs) {
    	value = rhs;
    	return this;
    }

    @property int value() const  { return _value; }
    @property void value(int newValue) { _value = newValue; }

    alias this value;
}

void main(){
    NoZero a = 1;
    auto b = a; // copyable
    // --b; would assert
    auto c = b+1;
    auto d = c+a;
}

Note the implementation is incomplete, still need to implement comparison to bool, operator'!' , unary '+', '-', '~', checks for overflows. It's a bit of work but I think it can be done.
June 17, 2012
On Sun, Jun 17, 2012 at 10:20 AM, Guillaume Chatelet <chatelet.guillaume@gmail.com> wrote:

Nice.

You can also add:

   this(NoZero nz) { _value = nz._value; }

and

   ref NoZero opAssign(NoZero rhs) {
       value = rhs._value;
       return this;
   }

this allows code like this:

a = a-a; // asserts
a = a*0; // asserts

An obvious generalization is to template the struct on a predicate:

struct Constrained(T, alias pred) {
   private T _value;

   invariant() { assert(pred(_value)); }

   this(T x) { _value = x; }
   this(Constrained c) { _value = c._value; }

   Constrained opBinary(string op)(const ref Constrained rhs) {
       return Constrained(mixin("this._value"~op~"rhs._value"));
   }
   Constrained opBinary(string op)(T rhs) {
       return Constrained(mixin("this._value"~op~"rhs"));
   }
   ref Constrained opUnary(string op)() if(op=="++"||op=="--"){
       mixin(op~"this._value;");
       return this;
   }
   ref Constrained opAssign(T rhs) {
       value = rhs;
       return this;
   }
   ref Constrained opAssign(Constrained rhs) {
       value = rhs._value;
       return this;
   }

   @property T value() const  { return _value; }
   @property void value(T newValue) { _value = newValue; }

   alias this value;
}

Constrained!(T,pred) constrained(alias pred, T)(T _value){
    return typeof(return)(_value);
}

void main(){
   auto a = constrained!(x => x > 0)(1);
   auto b = a;
   auto c = b+1;
   auto d = c+a;
   a = b-d;
}
June 17, 2012
On 06/17/12 14:23, Philippe Sigaud wrote:
> An obvious generalization is to template the struct on a predicate:
> 
> struct Constrained(T, alias pred) {
>    private T _value;
> 
>    invariant() { assert(pred(_value)); }
> 
>    this(T x) { _value = x; }
>    this(Constrained c) { _value = c._value; }
> 
>    Constrained opBinary(string op)(const ref Constrained rhs) {
>        return Constrained(mixin("this._value"~op~"rhs._value"));
>    }
>    Constrained opBinary(string op)(T rhs) {
>        return Constrained(mixin("this._value"~op~"rhs"));
>    }
>    ref Constrained opUnary(string op)() if(op=="++"||op=="--"){
>        mixin(op~"this._value;");
>        return this;
>    }
>    ref Constrained opAssign(T rhs) {
>        value = rhs;
>        return this;
>    }
>    ref Constrained opAssign(Constrained rhs) {
>        value = rhs._value;
>        return this;
>    }
> 
>    @property T value() const  { return _value; }
>    @property void value(T newValue) { _value = newValue; }
> 
>    alias this value;
> }
> 
> Constrained!(T,pred) constrained(alias pred, T)(T _value){
>     return typeof(return)(_value);
> }
> 
> void main(){
>    auto a = constrained!(x => x > 0)(1);
>    auto b = a;
>    auto c = b+1;
>    auto d = c+a;
>    a = b-d;
> }

Very nice :)
June 17, 2012
On 6/17/12 1:38 AM, Philippe Sigaud wrote:
> On Sat, Jun 16, 2012 at 9:22 PM, bearophile<bearophileHUGS@lycos.com>  wrote:
>
>> I see. is that semantically different from this (beside being shorter)?
>>
>> struct NoZero {
>>     int value;
>>     this(int x) { value = x; }
>>     alias value this;
>>     invariant() { assert(value != 0); }
>> }
>> void main() {
>>     auto a = NoZero(5);
>>     auto b = NoZero(0);
>> }
>
> The invariant isn't invoked through alias this, it seems:
>
> void main() {
>     auto a = NoZero(5);
>     a = 0; // compiles and runs happily
> }

To avoid this, expose an rvalue via alias this, not an lvalue. Then implement opAssign etc.

Andrei
June 17, 2012
On Sun, Jun 17, 2012 at 3:11 PM, Guillaume Chatelet <chatelet.guillaume@gmail.com> wrote:
> On 06/17/12 14:23, Philippe Sigaud wrote:

>> void main(){
>>    auto a = constrained!(x => x > 0)(1);
>>    auto b = a;
>>    auto c = b+1;
>>    auto d = c+a;
>>    a = b-d; // Boom!
>> }
>
> Very nice :)

The new syntax for lambdas is delightful to use. A bunch of aliases should give you the X10 examples.

x => ... still has some problem as it's a template, but using a standard module-level function gives functionalities akin to X10:

bool pos(int x) { return x > 0;}
alias Constrained!(int, pos) Positive; // works

// alias Constrained!(int, (int x) { return x > 0;}) Positive; // works
// alias Constrained!(int, (int x) => x > 0) Positive; // works
// but:
// alias Constrained!(int, (x) { return x > 0;}) Positive; // ka-boom!
// alias Constrained!(int, x => x > 0) Positive; // no such luck



enum OK = Positive(1); // OK
enum NOK = Positive(-1); // Compiling error, cool!

So this works. That's nice to know, I never used an assert inside a CT evaluation.

Hmmm, validating XML and/or SQL queries at CT...
June 17, 2012
On 06/17/12 15:53, Philippe Sigaud wrote:
> x => ... still has some problem as it's a template, but using a standard module-level function gives functionalities akin to X10:
> 
> bool pos(int x) { return x > 0;}
> alias Constrained!(int, pos) Positive; // works
> 
> // alias Constrained!(int, (int x) { return x > 0;}) Positive; // works
> // alias Constrained!(int, (int x) => x > 0) Positive; // works
> // but:
> // alias Constrained!(int, (x) { return x > 0;}) Positive; // ka-boom!
> // alias Constrained!(int, x => x > 0) Positive; // no such luck

I ran into that problem also. I usually tend to define a function for readability. It would be nice to have it working though, the last one is definitely neat.

> enum OK = Positive(1); // OK
> enum NOK = Positive(-1); // Compiling error, cool!
> 
> So this works. That's nice to know, I never used an assert inside a CT evaluation.
> 
> Hmmm, validating XML and/or SQL queries at CT...

Yummy !
June 17, 2012
On 6/16/12 10:26 AM, Guillaume Chatelet wrote:
> 'Elements of Programming' is an amazing book by Alexander Stepanov and
> Paul McJones ( M. Stepanov is the primary designer of the C++ STL ).
[snip]
> So without further ado, here is my humble first attempt
> https://github.com/gchatelet/phobos/blob/traits_concepts/std/traits2.d
> And the associated more readable DDoc
> http://bbteam.fr/traits2.html
>
> I would like to hear what you think.

This is a good idea because the traits are useful within and outside the algorithms discussed in EoP.

However, I don't think we need to abide strictly to the nomenclature (e.g. some of the stuff in EoP was already defined with a different name) although some EoP names are more mathematicky (such as "Codomain" vs. "ReturnType"). One issue is when EoP goes off and defines its own terms such as "regular type", which is usually known as "value type". In fact, "regular type" means something completely different in PL research. EoP's terminology didn't catch up outside C++ and sometimes outside a small group within it.

If I'm allowed to venture an opinion on EoP itself, it's an interesting book but I don't find it as big or great as some of its fans believe. It's not breaking any new ground, instead it explores more along already well-trodden territory, and it fails to find new mother lodes. Some (many?) chapters (such as transformations and orbits) describe some self-important notions but fail to demonstrate their general applicability. Nevertheless, the code really is exquisitely written, and studying it has significantly changed my approach and style in implementing algorithms.


Andrei