-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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 support for user defined literal expressions. #263
Comments
This proposal requires a lexical grammar. Please provide one. |
Eh, grammar. Grammar is overrated 😄 Now seriously, if you want user defined literals you'd better restrict them to being built from existing literals. As in |
What is the use case for this proposal? What problem is it trying to solve? |
I believe the use case is to make I think that use case is valid and nice, but I don't think this proposal is the right way to do that. I'd rather see |
My main motivation is for things like Defaulting parameters. Where you want to allow a defaulted value to be used but the
I could also create overloads to emulate defaulted parameters, but that leads to code bloat, all because I can't specify a nicer. @mikedn, Actually, cribbing more from C++ perhaps we can consider using a different marker and use a different bracketing to support it. Maybe, |
But what has this to do with C++? What you're describing is not what C++ does, C++ does what I said in my previous post. And anyway, why write something like
That will never work, the result of |
True in which case the correct overload should be used, but that should be used anyways.
It definitely is more work than this proposal, but it's orders of magnitude's nicer than this method. This proposed feature would just be a temporary fix that would be ditched if The language doesn't need to do the big jump that |
Hrm, there seems to be some serious confusion here.
|
This is what I mean. Doing so would solve the underlying use case without introducing new literal syntax and being more understandable to how developers would think it works. (Very often I hear how frustrating it is that the compiler is too "dumb" to now that something is "clearly" constant).
Hmm so either way it can't be expressed easily in the CLR. If either proposal wants to move forward there's two choices then, either the CLR could be expanded to understand other defaults, or the compiler could fake the optional parameters with overloads (perhaps using some attributes to mark to consuming compilers that this is actually an optional parameter). I vote for holding off on this until the team decides on doing something with |
You could encode the string value in the metadata. So, code like this: void M(string s = "foo", MyDate date = "2015-12-31"_MyDate)
…
M(); would compile into: M("foo", MyDate.op_suffix("2015-12-31")); Though this means that the value would be parsed every time the method is called, which is not ideal. |
The VB compiler and C# both already have some mechanism for this behavior. C# allows Decimal, and VB also allows datetime literals to be treated as constants. This data is encoded in an attribute when you use it as a default or as a constant field, and when you use it in code the compiler replaces it with a call to the constructor. |
Yes, you can use decimal as a default value but that's because there's a special attribute - Does this mean that when you add a user defined literal operator to a type the compiler should automatically generate such an attribute? Sounds kind of ugly because the public surface of the library containing the type will end up containing another type that's really just an implementation detail. And assuming we go the custom attribute way, what has this to do with user defined literals? It seems to me that one could simply mark a constructor as "const/literal/pure/etc." to make this happen, similar to what @mirhagk suggested but only for constructors which have only primitive types as parameters. Or, if you only care about DateTime, the C# compiler could simply treat some of the DateTime constructors specially and generate the DateTimeConstantAttribute. The language doesn't need to support DateTime literals to solve this problem. |
This proposal is not actionable because it does not propose a lexical grammar. Please reopen if you make the proposal much more specific. |
I know this is closed but this came up in #2401. The attributes that allow the compilers to "fake" literals for |
Please find my take at requested lexical grammar below (includes portions of existing grammar for context, three dots indicate omitted text). Could you please re-open (unless it's better to create a new one)? ... operator-declarator: ... literal: ... Used sources: https://msdn.microsoft.com/en-us/library/aa664812(v=vs.71).aspx |
Maybe this could be a more general feature of C# 7 subsuming the #215? |
@dsaf Can you please add some motivating examples and a description of the semantics? I don't see how that lexical grammar supports the use case described in the original post. |
I misread, #215 is not a dup. |
@gafter Thank you for reviewing. In terms of motivating examples to me personally it would help with custom attributes: public class Sample
{
[DefaultValueInt(123)]
public int Index { get; set; }
[DefaultValueDecimal(123m)]
public decimal Price { get; set; }
[DefaultValueVector2("15,20"_V2)]
public Vector2 Position { get; set; }
[DefaultValueDateTime("2015-05-02"_DateTime)]
public DateTime Position { get; set; }
[DefaultValueGuid("{9B37C7A8-63DA-4D8A-9FC6-DCBCF162D471}"_Guid)]
public Guid UniqueId { get; set; }
}
public class DefaultValueIntAttribute : Attribute
{
public DefaultValueIntAttribute(int defaultValue) { DefaultValue = defaultValue; }
public int DefaultValue { get; private set; }
}
public class DefaultValueDecimalAttribute : Attribute
{
public DefaultValueDecimalAttribute(decimal defaultValue) { DefaultValue = defaultValue; }
public decimal DefaultValue { get; private set; }
}
public class DefaultValueVector2Attribute : Attribute
{
public DefaultValueVector2Attribute(Vector2 defaultValue) { DefaultValue = defaultValue; }
public Vector2 DefaultValue { get; private set; }
}
public class DefaultValueDateTimeAttribute : Attribute
{
public DefaultValueDateTimeAttribute(DateTime defaultValue) { DefaultValue = defaultValue; }
public DateTime DefaultValue { get; private set; }
}
public class DefaultValueGuidAttribute : Attribute
{
public DefaultValueGuidAttribute(Guid defaultValue) { DefaultValue = defaultValue; }
public Guid DefaultValue { get; private set; }
}
public struct Vector2
{
public float X { get; set; }
public float Y { get; set; }
public static Vector2 literal operator _V2(string value)
{
//...
}
} In above code only the I find it weird that even |
@gafter Regarding semantics description - I am not too sure what you had meant. VB.NET already supports |
@dsaf I don't see how that would help with attributes. The limitation there is that only very specific types can be encoded into the attribute BLOB metadata that the CLR then uses to reconstruct the instance at run time through reflection. Your helper methods couldn't come into play either during compilation or at runtime. |
@HaloFour do you mean it would involve CLR changes? |
Yes. How the metadata is encoded is a part of the ECMA-355 CLI spec Even though C# fakes When it comes to default parameter values things are a little more forgiving since the default value is actually embedded at the callsite by the compiler. When you define a default value for a |
As C# support scripting mode, it should be possible to declare compile-time evaluated values in code. public const DateTime MyDate = DateTime.Parse("2015-02-05");
If default value for method parameter can't be evaluated at compile-time it would result in error "expression NNN can not be evaluated at compile time". |
The compiler doesn't really know whether the method is deterministic or whether it has side-effects, this is the reason in C++ we need to mark our methods with Doing some heuristics takes time and it will be very slow, complex and finally not safe. |
@eyalsk
I'd prefer first approach. .NET compilers, runtime and common base libraries absolutely needs to mark code paths and type members as deterministic or not and as having side-effects or not. In many cases compiler would automatically infer if code path is deterministic and haven't side effects. If method doesn't change any state outside of its body and don't invoke any nondeterministic nor side-effected code, method body is inherently deterministic and could be marked by compiler as deterministic. |
No heuristics are needed. Let's take conservative approach and mark code during compilation as deterministic so those evaluations are "cached" and transfered in binary form. It would end up at some form of dependency graph (possibly having cycles). Change or fetch of some state at method other than from its arguments would immediately make method nondeterministic. Any invokes of already known as nondeterministic members would also make method nondeterministic unless this code path is never executed actually and it's clearly known at compile time. One thing to consider (if we talk about this proposal) is the recursion and infinite cycle as method with infinite recursion or infinite cycle can be heuristically "pure", but any attempts to execute it would result in stack overflow, out of memory or just never finished so it would crash or hang compiler. And even if cycle is formally finite, taking 100 billion of iterations would result in effectively compiler hang. There is no much difference between hang and 8 hours of execution practically. |
How would the compiler infer it? and if it can why do we need an attribute? it can compute the result and emit the result directly.
Can you give an example? because I can't really see how you got to this conclusion. :)
Yeah, there's some recursion depth limit, can't remember what it is but it's fair. |
Because without it wouldn't be possible for compiler to make decisions on 3-rd party or framework code. So, to expose this to consumers it require to mark assembly public API with attributes.
Just we take conservative approach and make assumption of "nondeterministic if doesn't proved opposite". Interaction with external (to method body) state except that its parameters don't always make method nondeterministic, but it's reasonable to think that if method don't interact with external state it's deterministic. public class TestClass
{
// this method is pure as it don't interact with anything except method argument.
public int Method1(int arg)
{
return arg + 10;
}
private int someVal;
public void MutateState(int v)
{
someVal = v;
}
public int Method2(int arg)
{
return arg + someVal; // OOPS, we fetch mutable value here - method isn't deterministic in any fashion
}
private readonly int immutableVal; // assigned in constructor and immutable
public int Method2(int arg)
{
return arg + immutableVal; // This method is deterministic only in the object instance context as it fetch immutable deterministic instance field
}
private static readonly int staticallyImmutableVal = 5;
public int Method3(int arg)
{
return arg + staticallyImmutableVal; // This method is strongly deterministic as it reference static immutable deterministic field
}
private static readonly int randomValue = (new Random()).NextInteger();
public int Method4(int arg)
{
return arg + randomValue; // This method is nondeterministic as randomValue invoke non-deterministic member of Random and so randomValue field itself considered inherently nondeterministic
}
} |
Such literals would be quite useful in e.g such case.
|
why is 10s equal to 10000? Seems to only make sense in contexts where you need milliseconds. That's why things like TimeSpan are useful, as they make it completely clear what you're doing:
If you really wanted s, m, h, and whatnot to be operators, it would make much more sense for them to return TimeSpans. |
Of course in context of OOO TimeSpan is much better. Just wanted to show a bit better example of usage than parsing dates. At least in my opinion and from what I've seen in C++11 usages. |
It's not clear to me exactly why this was closed. The initial request was for a grammar, but isn't that kind of an implementation detail at the end of the day? Allowing user-created literal suffixes would be so cool with several different types of data:
It would also allow to potentially combine multiple suffixes to have "multiplicative" suffixes (kilo, kibi, centi, nano...) with the units themselves. Honestly, the examples in the OP and some of the follow-up discussions seem pretty weak to me and of very narrow usage. Representing real-word math and physics data seems so much more impactful. Say I want "1 hour and 15 minutes". var value = TimeSpan.FromHours(1) + TimeSpan.FromMinutes(15); With fluent extensions methods, it gets better: var value = 1.Hour() + 15.Minutes(); But literals would be a vast improvement still: var value = 1h + 15m; And could even be expanded to something that accepts "multiple inline suffixes", like: var value = 1h15m; For bytes, you could do (preferably with a custom var totalMemory = 15kiB; // => 15 * 1024 bytes |
Language change requests go through dotnet/csharplang now, not roslyn. Roslyn is the implementation of the language, not the place to design the language itself :-)
|
Got it. Is there a similar proposal on csharplang that we could link with this for future readers/people interested like myself?
Hadn't come to my mind while posting. Yeah that's tough... I guess such a system would have to have some sort of ambiguity resolution where if multiple suffixes are found in scope, the selection would be based on target type (not too dissimilar to "target-typed new"), so you'd have to do: TimeSpan value = 1h + 15m; or decimal value = 15m; |
A user defined literal expression would be defined as a new operator on a class or value type. The literal would be typed without quotes and must end with
_TypeName
This should help disambiguate it from existing literals.Only one literal operator can be declared per type and it must return the type in question.
e.g.
A literal will be considered a constant expression, and as a result will also be allowed as a const and defaulted expression. Since IL will not view it as such, the compiler will emit an attribute when declared as a
const
or a defaulted parameter containing the string literal and on usage will emit an invoke to theTypeName.op_suffix(string)
.Open questions:
How can the compiler not get confused by the literal? Should spaces and operators be disallowed?
(e.g. is
2015-12-31_MyDate
is that going to translate toMyDate.op_suffix("2015-12-31")
or will that be confused and thought of as2015 - 12 - MyDate("31")
?The literals might become very unwieldy if generics are in the mix. Should we allow custom names? If so how to handle disambiguation?
While this is cool, what if I'm feeling very jealous of the VB guys and want a constant datetime how would I go about doing that. Will we be allowed to add them to existing types?
void Scheduler(DateTime start,DateTime cutOff= 2015-12-31T23:59:59Z_DateTime)
That could be really nice.
The text was updated successfully, but these errors were encountered: