Skip to content

Comments

Struct Returned findSplit*() with implicit bool conversion#3288

Merged
andralex merged 1 commit intodlang:masterfrom
nordlow:struct-returned-findSplit
Oct 29, 2015
Merged

Struct Returned findSplit*() with implicit bool conversion#3288
andralex merged 1 commit intodlang:masterfrom
nordlow:struct-returned-findSplit

Conversation

@nordlow
Copy link
Contributor

@nordlow nordlow commented May 16, 2015

This enables the following useful syntax:

if (const split = line.findSplit(`-`))
{
    // do something with split
}

instead of current

const split = line.findSplit(`-`)
if (!split[1].empty)
{
}

for the Phobos algorithms findSplit, findSplitBefore and findSplitAfter.

See also discussion at: http://forum.dlang.org/thread/etttlwazcguwpaltyrps@forum.dlang.org#post-tbnvyqawcqmccvociwrr:40forum.dlang.org

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be public. There should never be a need for the user to create instances of FindSplitResult; they can already use Tuple in these cases.

edit:

Ditto for the others.

@JakobOvrum
Copy link
Contributor

A documented unittest example that illustrates the benefits gained from this patch would be appreciated.

@JakobOvrum
Copy link
Contributor

It seems CTFE-evaluation of findSplit fails with my patch. Is this fixable?

Tuple!(T, "foo") subtypes Tuple!T as an lvalue through a reinterpreting cast, which is not compatible with CTFE. It would be easy to support CTFE for subtyping Tuple!T as an rvalue, but it's not possible to conditionally AliasThis depending on CTFE.

It may be possible to support the subtyping as lvalue by redesigning the implementation of Tuple, such as making Tuple!T a field of Tuple!(T, "foo"). I might have a go at it.

edit:

I think the structs should be public and documented, though. It would be the natural way to document that they are implicitly convertible to Tuples as well as the behaviour on casting to boolean. There's just no need for the user to construct them, which questions the general usefulness of convenient constructor functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks compatibility. Using a subtype of Tuple!(R1, R1, R2) should be acceptable, but inserting the names is not. I think the rest of the PR does pull its weight.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tuple!(R1, "pre", R1, "separator", R2, "post") is in turn a subtype of Tuple!(R1, R1, R2), so it should be backwards-compatible. The issue is that this subtyping uses a reinterpret cast which precludes CTFE.

@nordlow
Copy link
Contributor Author

nordlow commented May 17, 2015

Removing the member names from structs made make unittest pass on my machine. Thanks!

Two things now remain:

  • Comment structs FindSplit*. Do you have any other nicely documented structs I can copy from?
  • Add documenting unittest thats makes use of the now more elegant pattern matching syntax. I'm guessing one for each; findSplit, findSplitBefore, findSplitAfter.

Guidelines for these are welcome. I'm a newbie in this regard.

BTW: Can you think of any other existing Phobos' algorithms that could benefit in this same regard?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these guys Voldemort types inside the respective functions. In the documentation mention "returns a subtype of Tuple!(...) with opCast defined for bool" etc. Then we should be good to go.

@andralex
Copy link
Member

So it seems voldemort + docs - convenience = win.

@nordlow
Copy link
Contributor Author

nordlow commented May 19, 2015

Should I use

auto pre = longPreExpr;
auto sep = longSepExpr;
auto post = longPostExpr;
return FindSplit!(typeof(pre), typeof(sep), typeof(post))(pre, sep, post);

to reduce source code size?

@nordlow
Copy link
Contributor Author

nordlow commented May 19, 2015

Should I change the names of all the structs to Result for compactness now that they are scoped Voldemort types?

And static i presume? What effect does static have for function-scoped structs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really pattern matching. It might be better to say something like "this enables the convenient idiom shown in the following example".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@MetaLang
Copy link
Member

It seems a little redundant to define the exact same struct three times. Maybe use a mixin template or make the structs non-Voldemort types?

@andralex
Copy link
Member

Yes, Result is fine. A pattern I've used is to call them Voldemort since the word has entered the D vernacular so it's evocative enough. static ensures the struct has no environment pointer. The duplication is minor so I'd go with it instead of factoring a type we then need to worry about.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for these to be templates.

No need for these constructors, just use Result(tuple(....)).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was mistaken about the first point. You may want to use distinct names from R1 and R2 as they might get confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand you last comment:

I'm guessing you mean that I should keep them templates but change the naming of template parameters R1, R2, and R3 so they don't conflict with template parameters of the outer scope.

Am I right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the parameters from Rx to Sx.

@nordlow
Copy link
Contributor Author

nordlow commented May 26, 2015

Ping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A documented unittest would be superb...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I remove the Example in this comment, then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK documented unittest will be part of example section auto-magically with the added benefit of being tested. See around Phobos the idiom must be applied often enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With documented unittest do you mean that I should put the documentation in line comments?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, documented unittest means writing a unittest block with a ddoc comment preceding it. The compiler will insert the body of the unittest into the generated ddocs. For example:

/// My awesome function
auto myFunc(R)(R range) { ... }

///
unittest
{
    // The code here will be part of the generated ddocs.
}

@DmitryOlshansky
Copy link
Member

LGTM the duplication is ugly though.

@MetaLang
Copy link
Member

Would any code rely on the return type of findSplit passing std.typecons.isTuple? That seems like it could be a potential point of breakage.

@nordlow
Copy link
Contributor Author

nordlow commented Aug 2, 2015

@MetaLang: Do you want me to check for backwards compatibility?

@DmitryOlshansky
Copy link
Member

@MetaLang: Do you want me to check for backwards compatibility?

I'd like to see it too.

@DmitryOlshansky
Copy link
Member

And please squash all commit into one

@nordlow
Copy link
Contributor Author

nordlow commented Aug 3, 2015

Unfortunately this breaks with compatibility of isTuple. We have asTuple though:

auto a = "Carl Sagan Memorial Station";
auto r = findSplit(a, "Velikovsky");
import std.typecons : isTuple;
static assert(isTuple!(typeof(r.asTuple)));
static assert(!isTuple!(typeof(r)));

What to do?

@DmitryOlshansky
Copy link
Member

@nordlow how about alias-this Tuple!(T) not just T ?

@nordlow
Copy link
Contributor Author

nordlow commented Aug 3, 2015

In findSplit we currently have

Tuple!(S1, S1, S2) asTuple;
alias asTuple this;

Can you be more precise on what you mean by:

Tuple!(T) not just T

?

@DmitryOlshansky
Copy link
Member

Can you be more precise on what you mean by:

Tuple!(T) not just T

?

Ehm I thought it wasn't. NVM

@DmitryOlshansky
Copy link
Member

We may be looking into generalizing isTuple still

@nordlow
Copy link
Contributor Author

nordlow commented Aug 3, 2015

Should that generalization of isTuple happen before this is merged?

@DmitryOlshansky
Copy link
Member

@nordlow there is no hurry since 2.069 is at least 2 months away but I'd much prefer to have a pull hanging around by the time this pull is merged.

@nordlow
Copy link
Contributor Author

nordlow commented Aug 3, 2015

Ok. Tell me when.

@DmitryOlshansky
Copy link
Member

Tell me when.

Pick any time from today to ~ October, 3rd ;)

@nordlow
Copy link
Contributor Author

nordlow commented Aug 3, 2015

Ahh, you mean you want me to look into generalizing isTuple, then?

If so, ideas anyone, on how to accomplish this?

@DmitryOlshansky
Copy link
Member

Ahh, you mean you want me to look into generalizing isTuple, then?

Yup, I have no plans to extend it for now.

By the look of it you should somehow trigger implicit conversion:

template isTuple(T)
{
    static if (is(Unqual!T Unused : Tuple!Specs, Specs...))
    {
        enum isTuple = true;
    }
    else
    {
        enum isTuple = false;
    }
}

I'd try __compiles/is(typeof(...)) with a local function that accepts Tuples with the above check, something like:

is(typeof((T t){
 void f(Spec...)(Tuple!Spec tup){ }
  f(t);
 }))

}

@nordlow nordlow force-pushed the struct-returned-findSplit branch 2 times, most recently from eceef8a to 4232b4d Compare September 3, 2015 08:17
@nordlow
Copy link
Contributor Author

nordlow commented Sep 3, 2015

I relaxed the definition of isTuple for backwards compatibility.

I added an assert for checking that return types of findSplit* now fullfils isTuple.

AFAICT, things should work now.

An alternative approach which I couldn't figure out the syntax for is

enum isTuple(T) = isImplicitlyConvertible!(T, Tuple!Specs, Specs...);

@nordlow nordlow force-pushed the struct-returned-findSplit branch from 4232b4d to 01e9aa1 Compare September 15, 2015 19:58
@nordlow nordlow force-pushed the struct-returned-findSplit branch from 01e9aa1 to 100e7a4 Compare October 5, 2015 19:56
@nordlow
Copy link
Contributor Author

nordlow commented Oct 5, 2015

@andralex Ping! I believe this is ready now!

@MetaLang
Copy link
Member

MetaLang commented Oct 5, 2015

@nordlow Try this:

enum isTuple(T, Specs...) = isImplicitlyConvertible!(T, Tuple!Specs);

It's pretty impressive that D can work backward from the instantiation of Tuple to infer Specs.

@nordlow
Copy link
Contributor Author

nordlow commented Oct 5, 2015

enum isTuple(T, Specs...) = isImplicitlyConvertible(T, Tuple!Specs);

fails as

typecons.d(1384,53): Error: cannot pass type int[2] as a function argument
typecons.d(1384,56): Error: cannot pass type Tuple!() as a function argument
typecons.d(411,49): Error: template instance std.typecons.isTuple!(int[2]) error instantiating
typecons.d(565,13):        instantiated from here: areBuildCompatibleTuples!(Tuple!(int, int), int[2])
typecons.d(1384,53): Error: cannot pass type Tuple!(int, int, int) as a function argument
typecons.d(1384,56): Error: cannot pass type Tuple!() as a function argument
typecons.d(411,49): Error: template instance std.typecons.isTuple!(Tuple!(int, int, int)) error instantiating
typecons.d(565,13):        instantiated from here: areBuildCompatibleTuples!(Tuple!(double, double, double), Tuple!(int, int, int))
typecons.d(579,20): Error: None of the overloads of '__ctor' are callable using argument types (Tuple!(int, int, int)), candidates are:
typecons.d(517,13):        std.typecons.Tuple!(double, double, double).Tuple.this(double, double, double)
typecons.d(1384,53): Error: cannot pass type Tuple!(double, string) as a function argument
typecons.d(1384,56): Error: cannot pass type Tuple!() as a function argument
typecons.d(396,55): Error: template instance std.typecons.isTuple!(Tuple!(double, string)) error instantiating
typecons.d(605,13):        instantiated from here: areCompatibleTuples!(Tuple!(int, string), Tuple!(double, string), "==")
typecons.d(612,13): Error: template instance std.typecons.Tuple!(int, string).areCompatibleTuples!(const(Tuple!(int, string)), Tuple!(double, string), "==") error instantiating
typecons.d(1384,53): Error: cannot pass type Tuple!(int, string) as a function argument
typecons.d(1384,56): Error: cannot pass type Tuple!() as a function argument
typecons.d(396,55): Error: template instance std.typecons.isTuple!(Tuple!(int, string)) error instantiating
typecons.d(605,13):        instantiated from here: areCompatibleTuples!(Tuple!(double, string), Tuple!(int, string), "==")
typecons.d(612,13): Error: template instance std.typecons.Tuple!(double, string).areCompatibleTuples!(const(Tuple!(double, string)), Tuple!(int, string), "==") error instantiating
typecons.d(624,20): Error: template std.typecons.Tuple!(double, string).Tuple.opEquals cannot deduce function from argument types !()(Tuple!(int, string)), candidates are:

Comint exited abnormally with code 1 at Mon Oct  5 22:55:57

@MetaLang
Copy link
Member

MetaLang commented Oct 5, 2015

Sorry, I missed the ! on isImplicitlyConvertible. I just made that exact same mistake a second time trying to test locally.

@nordlow
Copy link
Contributor Author

nordlow commented Oct 5, 2015

Sorry...

enum isTuple(T, Specs...) = isImplicitlyConvertible!(T, Tuple!Specs);

still fails

typecons.d(579,20): Error: None of the overloads of '__ctor' are callable using argument types (Tuple!(int, int, int)), candidates are:
typecons.d(517,13):        std.typecons.Tuple!(double, double, double).Tuple.this(double, double, double)
typecons.d(624,20): Error: template std.typecons.Tuple!(double, string).Tuple.opEquals cannot deduce function from argument types !()(Tuple!(int, string)), candidates are:
typecons.d(604,14):        std.typecons.Tuple!(double, string).Tuple.opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
typecons.d(611,14):        std.typecons.Tuple!(double, string).Tuple.opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
typecons.d(678,20): Error: template std.typecons.Tuple!(int, int, int).Tuple.opCmp cannot deduce function from argument types !()(Tuple!(int, int, int)), candidates are:
typecons.d(643,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(657,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(682,20): Error: template std.typecons.Tuple!(int, int, int).Tuple.opCmp cannot deduce function from argument types !()(Tuple!(int, int, int)), candidates are:
typecons.d(643,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(657,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(806,5): Error: template instance std.typecons.Tuple!(int, int) error instantiating
typecons.d(579,20): Error: None of the overloads of '__ctor' are callable using argument types (Tuple!(int, int, int)), candidates are:
typecons.d(517,13):        std.typecons.Tuple!(double, double, double).Tuple.this(double, double, double)
typecons.d(624,20): Error: template std.typecons.Tuple!(double, string).Tuple.opEquals cannot deduce function from argument types !()(Tuple!(int, string)), candidates are:
typecons.d(604,14):        std.typecons.Tuple!(double, string).Tuple.opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
typecons.d(611,14):        std.typecons.Tuple!(double, string).Tuple.opEquals(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "=="))
typecons.d(678,20): Error: template std.typecons.Tuple!(int, int, int).Tuple.opCmp cannot deduce function from argument types !()(Tuple!(int, int, int)), candidates are:
typecons.d(643,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(657,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(682,20): Error: template std.typecons.Tuple!(int, int, int).Tuple.opCmp cannot deduce function from argument types !()(Tuple!(int, int, int)), candidates are:
typecons.d(643,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(657,13):        std.typecons.Tuple!(int, int, int).Tuple.opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<"))
typecons.d(489,23): Error: template instance std.typecons.Tuple!(int, string, float) error instantiating
typecons.d(440,28):        instantiated from here: Tuple!(int, "id", string, float)
typecons.d(806,5):        instantiated from here: Tuple!(int, int)
typecons.d(441,13): Error: static assert  (is(Fields.Types == TypeTuple!(int, string, float))) is false
typecons.d(1322,20):        instantiated from here: Tuple!(int, string, double)
typecons.d(468,28):        instantiated from here: tuple!(int, string, double)
typecons.d(806,5):        instantiated from here: Tuple!(int, int)

Comint exited abnormally with code 1 at Tue Oct  6 00:51:32

@MetaLang
Copy link
Member

MetaLang commented Oct 5, 2015

Ah well, you can't win 'em all. It must be because my custom isTuple is in a different module and thus doesn't conflict with std.typecons' version. I tried messing around with the std.typecons definition but no dice.

It's a bit strange that this doesn't work in the first place; it looks like std.typecons.isTuple actually does check for implicit conversions.

template isTuple(T)
if (is(Unqual!T Unused: Tuple!Specs, Specs...))
{
    //Check if Unqual!T implicitly converts to Tuple!Specs
    static if (is(Unqual!T Unused : Tuple!Specs, Specs...)) 
    {
        enum isTuple = true;
    }
    else
    {
        enum isTuple = false;
    }
}

Which is exactly what you are checking. I thought that T: U is true if T has an alias this for U, so I really don't know why that isn't working.

@MetaLang
Copy link
Member

MetaLang commented Oct 5, 2015

The plot thickens:

enum isTuple(T) = is(Unqual!T: Tuple!Specs, Specs...);

unittest
{
    struct TupleLike(T...)
    {
        Tuple!T payload;

        alias payload this;
    }

    static assert(is(TupleLike!(): Tuple!()));                                                    //pass
    static assert(is(TupleLike!int: Tuple!int));                                                  //pass
    static assert(is(TupleLike!(int, real, string): Tuple!(int, real, string)));                  //pass
    static assert(is(TupleLike!(int, "x", real, "y"): Tuple!(int, "x", real, "y")));              //pass
    static assert(is(TupleLike!(int, TupleLike!real, string): Tuple!(int, Tuple!real, string)));  //fail (due to the inner TupleLike)

    static assert(isTuple!(TupleLike!()));                              //fail
    static assert(isTuple!(TupleLike!(int)));                           //fail
    static assert(isTuple!(TupleLike!(int, real, string)));             //fail
    static assert(isTuple!(TupleLike!(int, "x", real, "y")));           //fail
    static assert(isTuple!(TupleLike!(int, TupleLike!real, string)));   //fail
}

Looks like alias this still needs some work.

@nordlow
Copy link
Contributor Author

nordlow commented Oct 6, 2015

So...is this ready, @MetaLang ?

@MetaLang
Copy link
Member

MetaLang commented Oct 6, 2015

Looks good to me.

@andralex
Copy link
Member

Thanks for this work. It's right at the limit of being too clever for its own good, but I did notice that folks are unclear on what to test if the search succeeded.

@andralex
Copy link
Member

Auto-merge toggled on

@MetaLang
Copy link
Member

On the contrary, I like this pattern (aside from the need for alias this). It's a good-enough solution to D's lack of flow-based typing, which would be much more complicated to implement.

andralex added a commit that referenced this pull request Oct 29, 2015
Struct Returned findSplit*() with implicit bool conversion
@andralex andralex merged commit b6a61d9 into dlang:master Oct 29, 2015
@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

Could this reach 2.069?

@JackStouffer
Copy link
Contributor

@nordlow It's too late, the first release candidate for 2.069 is already out.

@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

Ok. No worries :)

@WalterBright
Copy link
Member

@nordlow
Copy link
Contributor Author

nordlow commented Apr 15, 2016

IMO, I think it's reasonable to not allow assignment between the two Voldermort types returned from findSplitAfter and findSplitBefore.

But it's ok for me to allow it if there's a way to allow assignment between them.

@nordlow nordlow deleted the struct-returned-findSplit branch October 19, 2021 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants