Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add std.typecons.HeadConst (Continuation of #2881) #3862

Closed
wants to merge 1 commit into from

Conversation

JakobOvrum
Copy link
Member

Ressurection of #2881. Fixes the issue @monarchdodra pointed out by using std.typecons.Proxy.

Github didn't give me the option to reopen the old PR after I force pushed the new changes.

if (!is(T == const) && !is(T == immutable))
{
private T HeadConst_value;
mixin Proxy!HeadConst_value;
Copy link
Contributor

Choose a reason for hiding this comment

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

When I comment this line out and add ref to the return type of HeadConst_get(), all of your unittests still pass.

How about adding another test or two documenting why Proxy is necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, I added the relevant test. Further attempts to break it are much appreciated!

Copy link
Member

Choose a reason for hiding this comment

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

A naive viewpoint is that the alias this that returns an rvalue should be enough to implement head constness. Could you please document (privately and if needed to the user as well) why the proxy is necessary?

@tsbockman
Copy link
Contributor

Further attempts to break it are much appreciated!

Since you asked...

void main(string[] args) {
    struct Fail {
        this(bool fail) { }
    }

    HeadConst!Fail fail = true; // Error: cannot access frame pointer of main.Fail
}

@JakobOvrum
Copy link
Member Author

Hm, I don't know how to make it work with non-static nested structs or classes. You should be able to do: auto fail = headConst(Fail(true));. There might be a possible fix using alias parameters.

@tsbockman
Copy link
Contributor

You should be able to do: auto fail = headConst(Fail(true));.

Yes, that works.

Hm, I don't know how to make it work with non-static nested structs or classes.

Can you at least detect such cases and print a better error message?

@JakobOvrum
Copy link
Member Author

Can you at least detect such cases and print a better error message?

Done.

if (__traits(compiles, T(args)))
{
static assert((!is(T == struct) && !is(T == union)) || !isNested!T,
"Nested type " ~ fullyQualifiedName!T ~ " must be constructed " ~
Copy link
Contributor

Choose a reason for hiding this comment

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

"Nested type T must be marked as static or constructed explicitly at the call-site", etc.

Since the error triggers even when the context is not actually being used for anything, just marking the type as static is going to be the best fix most of the time (at least with the way I tend to use nested types).

Copy link
Member Author

Choose a reason for hiding this comment

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

The term "nested type" is usually reserved for non-static nested types. If the user didn't make it static then it should be assumed that this is intentional. Error messages are not tutorials.

@tsbockman
Copy link
Contributor

@JakobOvrum

The term "nested type" is usually reserved for non-static nested types.

If so, then this is specific to D - C#, for instance has Nested Types which cannot capture their context at all.

If the user didn't make it static then it should be assumed that this is intentional.

If static were the default, I would agree. Since it's not though, it is entirely possible that the user simply assumed (correctly, I hope?) that the optimizer would remove the context pointer if it was not needed.

@JakobOvrum
Copy link
Member Author

If so, then this is specific to D - C#, for instance has Nested Types which cannot capture their context at all.

We should not consider C# semantics in our error messages.

If static were the default, I would agree. Since it's not though, it is entirely possible that the user simply assumed (correctly, I hope?) that the optimizer would remove the context pointer if it was not needed.

Performance and optimization is a complete red herring.

Telling the user to mark the type as static is not an option because such an action breaks code that uses the context. The current message is correct in all cases.

@tsbockman
Copy link
Contributor

We should not consider C# semantics in our error messages.

C# is just one example of many. I looked, and even the D language specification uses "nested" in a way that encompasses static types.

Performance and optimization is a complete red herring.

The point I was making, is that a user has no real incentive to mark a nested type as static unless it is required to make the code compile. This fact invalidates your claim: "If the user didn't make it static then it should be assumed that this is intentional."

Telling the user to mark the type as static is not an option because such an action breaks code that uses the context. The current message is correct in all cases.

My proposed message simply presents the user with an additional option. This will cause no problems for the user, unless he doesn't know what static does. Since "Error messages are not tutorials", this should not be a concern.

But, if you still want to argue I will let someone else answer, as I do not consider this detail to be a blocker.

At the moment, I see no other problems with this PR - except that, considering how hard it is to plug all the loopholes in a scheme like this, it might be best to run it through std.experimental first.

@JakobOvrum
Copy link
Member Author

I've disambiguated the error message a little.

This fact invalidates your claim: "If the user didn't make it static then it should be assumed that this is intentional."

It is the conservative estimate. Users that don't know the difference can safely follow the advice.

@tsbockman
Copy link
Contributor

I've disambiguated the error message a little.

Looks good. Thoughts on std.experimental?

@JakobOvrum
Copy link
Member Author

Looks good. Thoughts on std.experimental?

Last time we discussed using std.experimental as a staging area for additions to existing modules it was met with a lot of resistance.

edit:

A relevant consideration is that the interface of HeadConst should not be expected to change beyond fixing bugs.

@tsbockman
Copy link
Contributor

Last time we discussed using std.experimental as a staging area for additions to existing modules it was met with a lot of resistance.

Hmm. Seems like the biggest concern was uncertainty that anything would ever actually get migrated out of std.experimental once it went in.

I think HeadConst is a better candidate for the process in various ways than padLeft and padRight, but I guess it's better for it to go straight into std than get stuck in experimental indefinitely.

@schveiguy
Copy link
Member

Last time we discussed using std.experimental as a staging area for additions to existing modules it was met with a lot of resistance.

I will note that most of the resistance was for requiring all changes to go through std.experimental. I think it's fine to put new pieces of a module into std.experimental (make sure you public import std.typecons from std.experimental.typecons).

Fix ticket 13796
@dlang-bot
Copy link
Contributor

Fix Bugzilla Description
13796 A simple "array head const" struct for Phobos

assert(sboth.i == 42);

sboth.i = 24;
assert(sboth.i == 24);
Copy link
Contributor

Choose a reason for hiding this comment

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

For a C++ const struct field, shouldn't i stay constant?

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 preventing assignment for structs when fields can still be assigned to makes much sense.

@DmitryOlshansky
Copy link
Member

Looks sensible. How about we name it Final as in Java's final (i.e. write once)?

@PetarKirov
Copy link
Member

@DmitryOlshansky See the comments in the previous PR.
Also I think that Final is nice name for a type constructor like Proxy which would enable inlining by devirtualizing the methods of a class.

@JakobOvrum
Copy link
Member Author

I originally called it Final. See #2881.

@DmitryOlshansky
Copy link
Member

@JakobOvrum I'd still suggest Final if only for the reason that while HeadConst is pedantically correct it's an awful lot to type for a common need and for Final to be useful if it must not be a chore to use.

@tsbockman
Copy link
Contributor

Another vote for Final - as @DmitryOlshansky said, HeadConst is just a little bit too much typing to feel natural for something like this.

@PetarKirov
Copy link
Member

But, calling it Final would be inconsistent with the rest of the language! The only meaning that final has is in the context of inheritance, which is good. Just imagine exlaining to someone new to D that Final means head const and has nothing to do with final. Please keep such Java-isms out of D!

In the longer to term what I would like to see is:

  • HeadConst
  • TailConst - an alias to Rebindable

and either:

  • Nullable
  • NotNull - disallows the null value for reference types
    or:
  • Optional - an alias to Nullable
  • Required - same as NotNull

Symmetry in API design is important. Please don't break it, just to appeal to Java users.

@PetarKirov
Copy link
Member

Also anyone who doesn't like the correct name or doesn't feel like typing such a long name can just put alias in his code to Final, HC or even to some random non-ASCII unicode character.

@DmitryOlshansky
Copy link
Member

@ZombineDev I don't see this problem - just say that Final is for write once variables.
Head const is not immediately obvious to outsiders and implies extra reasoning to understand that const is transitive, and that there is a special form of tail-const and only then see where the idea of head-const comes in.

@PetarKirov
Copy link
Member

@DmitryOlshansky I don't think that an outsider would want to use HeadConst( or Final) if he doesn't understand that D's const is transitive. Only if D's transitive constness is unsuitable, one would want to reach for something as fine-grained as HeadConst or TailConst. BTW, we also have HeadUnshared which is used here.

Another reason why Final is bad name:
Let's take the following code for example class Person { string address; }. In which of the two examples below is more clear if it is ok to modify the address field?
A)

Final!Person person;
person.address = newAddress; // Is the allowed?! I thought that Person was write once?

B)

HeadConst!Person person;
person.address = newAddress;

@JackStouffer
Copy link
Member

I have to agree with @ZombineDev

@DmitryOlshansky
Copy link
Member

Another reason why Final is bad name:
Let's take the following code for example class Person { string address; }. In which of the two examples below is more clear if it is ok to modify the address field?

Yes, write one variables are like that - you can't rebin the name but you can overwrite all fields if that is allowed by its structure (i.e. they are all publicly accessible)

@DmitryOlshansky
Copy link
Member

Personally I don't care for a name just that I for one would not type HeadConst anywhere suitable the semantical gains vs visual noise ratio is too low. So if everyone is for HeadConst so be it.

@tsbockman
Copy link
Contributor

So if everyone is for HeadConst so be it.

Same here. Final is my preference, but I don't want to block this.

@ntrel
Copy link
Contributor

ntrel commented Mar 20, 2016

Technically this should be called Unassignable IMO. HeadConst!Struct makes me think the struct's (public) fields should be head const, like in C++, and maybe we'll want a std.typecons wrapper for that in future. But in practice Final is a good short name.

@PetarKirov
Copy link
Member

@ntrel HeadConst!Struct should be disallowed. HeadConst!T, just like TailConst!T (a.k.a. Rebindable!T) are only applicable when T is a reference type.

  • TailConst!(T*) -> const(T)*
  • TailConst!(const T*) -> const(T)*
  • HeadConst!(T*) -> T const(*)
  • HeadConst!(const T*) -> T const(*)

Obviously we need HeadConst because const(*) (or the equivalent for arrays and classes) can't be expressed in D.

The only meaning that final has is virtual(false). Let's keep it that way.

@ntrel
Copy link
Contributor

ntrel commented Mar 21, 2016

@ZombineDev:

HeadConst!T [is] only applicable when T is a reference type

This pull allows HeadConst!Struct, which is why Unassignable is a more accurate name (it disables opAssign). Unassignable!Struct can be (somewhat) useful by preventing private fields of a struct from being changed through assignment.

@DmitryOlshansky DmitryOlshansky added the @andralex Approval from Andrei is required label May 5, 2016
+
+ Head-const variables cannot be directly mutated or rebound, but references
+ reached through the variable are typed with their original mutability.
+ It is equivalent to $(D final) variables in D1 and Java, as well as
Copy link
Member

Choose a reason for hiding this comment

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

I think therefore Final would be a better choice than HeadConst

Copy link
Contributor

Choose a reason for hiding this comment

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

@andralex

I think therefore Final would be a better choice than HeadConst

Someone needs to make an executive decision about the name. It has already been debated (and changed once), with about a 50% / 50% split of votes:

For HeadConst: ZombineDev, JackStouffer, schuetzm, Dicebot

For Final: JakobOvrum, DmitryOlshansky, tsbockman (me), andralex

(Click the links if you want to skim the earlier discussion.)

@andralex
Copy link
Member

andralex commented Jun 5, 2016

OK. A good step forward is to put this in std.experimental, there's subtleties about it. Then we can defer zeroing in on a name.

@andralex
Copy link
Member

andralex commented Jun 7, 2016

For more detail: we should define std.experimental.typecons and put this in it. With that I preapprove this addition. Thanks!

@JackStouffer
Copy link
Member

std.experimental.typecons

Already exists :)

@wilzbach
Copy link
Member

wilzbach commented Aug 15, 2016

For more detail: we should define std.experimental.typecons and put this in it. With that I preapprove this addition. Thanks!

This has been preapproved for more than two months - is someone interested in adopting the PR?
Maybe we can have an "orphan" tag?

@wilzbach
Copy link
Member

This has been preapproved for more than two months - is someone interested in adopting the PR?
Maybe we can have an "orphan" tag?

I went ahead -> #4755 ;-)

@wilzbach
Copy link
Member

I went ahead -> #4755 ;-)

Wow that was a super-fast resurrection! It's alive with #4755.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@andralex Approval from Andrei is required
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants