March 03, 2018
On Saturday, 3 March 2018 at 18:28:42 UTC, aliak wrote:
> On Friday, 2 March 2018 at 19:47:23 UTC, SimonN wrote:
>
> If you know of other ways though I'm all ears :)
>
> Cheers

maybe not exactly what you want, but here are some templates i wrote a while ago which basically are a more flexible form of the ?. operator.

https://run.dlang.io/gist/598c59506699a0f81c3abbbaf7d5feee?compiler=dmd
March 03, 2018
On Saturday, 3 March 2018 at 18:28:42 UTC, aliak wrote:
> struct NonNull(T) if (isPointer!T || is(T == class))
> {
>   T value;
>   alias value this;
>   this(Args...)(Args args) { this.value = new T(args); } // always force creation
> }

The pitfall here is that all structs must be default-constructible, and then all of the fields have the static init value:

    class A { this(int) { } }
    void main()
    {
        NonNull!A a;
        assert (a.value is null);
    }

...compiles and passes.

Wrapping class references in structs is costly, we lose a lot of automatic conversions across subclasses, const/immutable, ... For the over 90 % of references where null is a bug (sign of forgotten assignment), it will be convenient to keep D's references. A library solution must be super-convenient to be worthwhile here. For weeks, I've put off re-discussing non-null refs directly in D because a language change is a last resort.

> I'm going to experiment with moving Optional's methods out
> If you know of other ways though I'm all ears :)

No ideas for now, but I'll certainly follow this development closely, and will be happy to test things!

-- Simon
March 04, 2018
On Saturday, 3 March 2018 at 20:52:25 UTC, SimonN wrote:

> The pitfall here is that all structs must be default-constructible, and then all of the fields have the static init value:
>
>     class A { this(int) { } }
>     void main()
>     {
>         NonNull!A a;
>         assert (a.value is null);
>     }
>
> ...compiles and passes.

Maybe this part can be fixed with:

struct NonNull(T) if (isPointer!T || is(T == class))
{
    static if (isPointer!T)
    	alias U = PointerTarget!T;
    else
        alias U = T;

    T value = new U();

    alias value this;

    @disable this(typeof(null)); // no no

    this(Args...)(Args args) {
        this.value = new U(args);
    }

    @disable opAssign(V)(V v) {} // no no no

    void opAssign(V)(NonNull!V other) { // only this allowed.
        this.value = other.value;
    }
 }

Tested a bit this time and added a few extra checks. (thinking out loud: Maybe the constraint should only be is(T == class) actually and we can keep this to reference types...)

The above will also fail right now if T is a class with a non default init. Maybe there's a way to fix that as well though.


>
> Wrapping class references in structs is costly, we lose a lot of automatic conversions across subclasses, const/immutable,

Ok this part would require some testing and playing around, can't really think of anything off the top of my head.

Can you provide an example case here? It might be workable with some (hurts my head to think about right now) combinations of auto ref/inout on initializers or opAssigns or something.

> No ideas for now, but I'll certainly follow this development closely, and will be happy to test things!

Glad to hear!



March 04, 2018
On Saturday, 3 March 2018 at 19:20:26 UTC, arturg wrote:
> On Saturday, 3 March 2018 at 18:28:42 UTC, aliak wrote:
>> On Friday, 2 March 2018 at 19:47:23 UTC, SimonN wrote:
>>
>> If you know of other ways though I'm all ears :)
>>
>> Cheers
>
> maybe not exactly what you want, but here are some templates i wrote a while ago which basically are a more flexible form of the ?. operator.
>
> https://run.dlang.io/gist/598c59506699a0f81c3abbbaf7d5feee?compiler=dmd

Aye, nice, I might steal that when stuff :D I also want to implement something like https://dlang.org/library/std/variant/visit.html

so Optional!T a;
a.visit!(
  (T value) => value here,
  () => no value here
);


March 04, 2018
On Sunday, 4 March 2018 at 12:03:32 UTC, aliak wrote:
> Maybe this part can be fixed with:
> struct NonNull(T) if (isPointer!T || is(T == class))
> {
>     @disable opAssign(V)(V v) {} // no no no
>     void opAssign(V)(NonNull!V other) { // only this allowed.

Interesting approach -- I'll keep it on the backburner for testing. Converting a class-heavy codebase to this would be daunting, but I don't want to rule it out yet.

>> Wrapping class references in structs is costly, we lose a lot
> Can you provide an example case here?

Automatic subtype detection will fail, and, thus, e.g., covariant return types.

To make it clearer that this isn't a bug in Optional, but rather an artifact of D's subtype detection, I will give examples with std.typecons.Rebindable instead of with Optional. For a class A, Rebindable!(const(A)) is designed as a reference to const(A) that can be rebound to point to a different const(A). I believe Rebindable!A aliases itself away and thus Rebindable!A will not be discussed, only Rebindable!(const(A)).

    import std.typecons;
    class Base { }
    class Derived : Base { }
    static assert(is(const(Derived) : const(Base))); // OK
    static assert(is(Rebindable!(const(Derived)) : Rebindable!(const(Base)))); // fails

Covariant return types will also fail. The following example doesn't compile, but would if we stripped all `rebindable' and all `Rebindable!':

    import std.typecons;
    class Base {
        Rebindable!(const(Base)) create()
        {
            return rebindable(new Base());
        }
    }
    class Derived : Base {
        override Rebindable!(const(Derived)) create()
        {
            return rebindable(new Derived());
        }
    }

Speculation: There may be problems once we try to wrap class references in two different structs. We might want Optional or NonNull, and then another wrapper (e.g., Rebindable) for some other benefit. When these wrappers are written with if-constraints to take only raw D class references, we may pick only one wrapper. But I haven't investigated this yet.

-- Simon
March 11, 2018
On Sunday, 4 March 2018 at 13:26:21 UTC, SimonN wrote:
> On Sunday, 4 March 2018 at 12:03:32 UTC, aliak wrote:
>> Maybe this part can be fixed with:
>> struct NonNull(T) if (isPointer!T || is(T == class))
>> {
>>     @disable opAssign(V)(V v) {} // no no no
>>     void opAssign(V)(NonNull!V other) { // only this allowed.
> Interesting approach -- I'll keep it on the backburner for testing. Converting a class-heavy codebase to this would be daunting, but I don't want to rule it out yet.

Heh yeah, it would be indeed!

> Automatic subtype detection will fail, and, thus, e.g., covariant return types.
>
> To make it clearer that this isn't a bug in Optional, but rather an artifact of D's subtype detection, I will give examples with std.typecons.Rebindable instead of with Optional. For a class A, Rebindable!(const(A)) is designed as a reference to const(A) that can be rebound to point to a different const(A). I believe Rebindable!A aliases itself away and thus Rebindable!A will not be discussed, only Rebindable!(const(A)).
>
>     import std.typecons;
>     class Base { }
>     class Derived : Base { }
>     static assert(is(const(Derived) : const(Base))); // OK
>     static assert(is(Rebindable!(const(Derived)) : Rebindable!(const(Base)))); // fails
>
> Covariant return types will also fail. The following example doesn't compile, but would if we stripped all `rebindable' and all `Rebindable!':
>
>     import std.typecons;
>     class Base {
>         Rebindable!(const(Base)) create()
>         {
>             return rebindable(new Base());
>         }
>     }
>     class Derived : Base {
>         override Rebindable!(const(Derived)) create()
>         {
>             return rebindable(new Derived());
>         }
>     }


Ah, thanks, that cleared things up!

Does this maybe boil down to if two templates should be covariant on their parameter types? Considering that static if allows you to do whatever you want inside based on static inference, in theory Rebindable!(const Derived) can be completely different than Rebindable(const Base). So are covariant return types even possible I wonder?

If D had implicit conversions then types can specifically say that they explicitly allow behavior like this where it made sense though.

>
> Speculation: There may be problems once we try to wrap class references in two different structs. We might want Optional or NonNull, and then another wrapper (e.g., Rebindable) for some other benefit. When these wrappers are written with if-constraints to take only raw D class references, we may pick only one wrapper. But I haven't investigated this yet.
>
> -- Simon

Aye, I guess Optional and NonNull would have to know about each other to make the most out of them together. I've actually been tempted to put in a NonOptional type inside the optional package. But maybe NonNull would make more sense since we may just want this behavior for reference types. and NonOptional!int is kinda ... well wtf is that :p

Cheers
- Ali


March 15, 2018
On Sunday, 11 March 2018 at 09:28:39 UTC, aliak wrote:
> Does this maybe boil down to if two templates should be covariant on their parameter types?

I'm not sure if this is always good. I haven't thought about it deeply, but I assume that some templated functions should be contravariant, and that there is no catch-all rule.

> inference, in theory Rebindable!(const Derived) can be completely different than Rebindable(const Base). So are covariant return types even possible I wonder?

I don't think covariant return types will work here with the 2018-03 language spec. We're returning structs (Rebindable!Something) and covariant return types is specifically for classes. :-)

It's one of the reasons why I shun wrapping types unless the benefit is really big.

> If D had implicit conversions then types can specifically say that they explicitly allow behavior like this where it made sense though.

Yeah -- it shifts another burden of implementation to library type authors, I'm not sure if this path should be followed, especially since implicit constructor calls are shunned in D. (I have no opinion whether C++'s implicit constructor calls are handy enough for the obscured program flow.)

* * *

Here's another example where wrapped class references fail: You can override opEquals in the class, and the struct's opEquals need not necessarily call that.

    import std.stdio;
    import std.typecons;

    class C {
        int x;
        override bool opEquals(Object rhsObj)
        {
            const(C) rhs = cast(const(C)) rhsObj;
            return this.x == rhs.x;
        }
    }

    void main()
    {
        C a = new C();
        C b = new C();
        assert (a == b); // OK: Even though a !is b, we overrode opEquals
                         // and compare a.x == b.x, which is true (0 == 0).

        Rebindable!(const(C)) ca = a;
        Rebindable!(const(C)) cb = b;
        assert (ca == cb); // Fails! struct Rebindable doesn't forward its
                           // opEquals to the class's opEquals!
    }

If this is by design, then it's very surprising. I've reported this as a bug to get a judgement from the Phobos authors: https://issues.dlang.org/show_bug.cgi?id=18615

-- Simon
1 2 3
Next ›   Last »