-
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
[Umbrella] How would you imagine constexpr in C#? #15079
Comments
If we are seeing multiple requests for some form of |
So, what result is from 0 / 0 ? |
@AdamSpeight2008 What? I'm not sure where you're coming from but this is A DISCUSSION about whether it make sense to have it in C# and if so how you see this fits; if not then why not? THIS ISN'T A PROPOSAL! I thought I was clear, if you don't think it make sense, please share your opinion about it. @vbcodec Infinity isn't a number or more specifically is not a constant in the range of values that can be evaluated at compile time and is not represented by any type not to mention the set of integers so this wouldn't compile in C++ at all, however, even without this rule, a terrible way is to recurse, reach the limit and finally throw so either way it's not going to crash the compiler. Revised answer. @Unknown6656 Yeah, this is the reason I created this post to discuss all of these and maybe come up with a generalized solution. :) |
@eyalsk You've misunderstood what I said. I'm say if people are asking for something like constexpr. We should ask why, and if we need to do something. |
@AdamSpeight2008 oh sorry, I thought you're asking why we need it. :) |
@eyalsk One issue I see with it is the example you reference uses templates. Touchy Subject |
@AdamSpeight2008 Yeah but how do you mean? I mean templates in C++ allows T to be any type so it's not type safe in general but then why it's an issue? I'm probably missing something. :) |
Templates and Macros have previously been brought up and respectively declined by the language design teams. #14619 |
@AdamSpeight2008 Sure but these two concepts are unrelated at all. In the example I provided templates are used because C++ doesn't support generics and so in order to support just any type in C++ you must use templates, in C# you would achieve exactly the same thing with generics. Templates and Macros are two different beasts in C++ and are used for different things but we don't really need them in C# and so declining them was reasonable beyond that they are completely orthogonal to |
Oh, perfect!
With "deterministically computable" I mean every method/expression, which is not dependent from "unpredictable" stuff like IO, network, streams, memory, filesystem, machine configuration, hardware, UI, interrupts, etc. Where do we draw the line between constant expressions, which should be pre-computed by the compiler and expression, which should be evaluated at runtime? |
Well, I think we can start with all primitive types except object, these are pretty good candidates for constants when it comes to functions I don't really know what it would mean in C# because we can't mark functions with const and we can't know whether a function is deterministic.
In my opinion yes but up to a certain recursion depth, I think that in Clang the default is 500 but I might be wrong.
Done! 😉
Yeah, totally but just like I said previously it must have some fair limits that will still allow some large computation but I don't know what would be the right limit here and what optimizations the compiler can do, after all I doubt we want very slow compilation times so this would be pretty challenging.
Well, before we draw the line we need to define the rules. 😄 |
I think the pre-processor should build a caller tree to sort the methods by complexity and recursion depth.
I think that we should have some kind of compiler hint or switch, like: [CompilerHint(MaxTime = 500, MaxRecursionDepth = 20)]
public const string MY_STRING = /* some incredibly complex constant expression */; To tell/hint the compiler, how much time it should use/"waste" for precompilation. But of course, this is not optimal, because it is not a scalable solution, as a "timeout" of 500ms might be much to great for a small 11KB-application, but much to small for a large project like an entire operating system written in .NET ...... I think that one should therefore define some kind of scalable method, which determines the maximum timeout.... |
Why it needs to be sorted? :) I guess what I'm asking is if you sort it how does this helps optimization?
I think that it's better to set the recursion depth as opposed to time limit like in Clang |
I think it would be better to optimize all "small" methods first and complex methods last, if you have an optimization time limit.
Agreed. This would invalidate my point of sorting the methods first by complexity. |
@eyalsk Constants expressions is not just about the compiler. When you have a JIT a constant expression at the JIT level behaves in the same way as a constant expression at the Compiler level. For example, pointer size. When you are JITting you know for sure and can act accordingly. So a C# constant expression should also have the ability to pull stuff from 'external' sources like the JIT/Runtime. |
@redknightlois Well, in C++ constant expressions is solely only the compiler's work, how do you propose to pull stuff from an external source? how would this work? |
@eyalsk That would be the responsability of the runtime, therefore they have to be marked as that. For example, for all purposes IntPtr.Size is fixed at JIT time, and the JIT actually use that knowledge to perform optimizations like dead code optimizations. If the JIT has the knowledge, then the JIT should evaluate any external binded expression as part of a late binding task. Perform also the usual optimizations on the code itself. Let's suppose that JIT external constants can be marked as:
The by-products when we have support for late binding of const expressions is that it becomes trivial for example to write code for specific architectures (think CpuID), figure out (and exploit) the size of a general T struct type (think T* here), define constants that currently cannot be constants because well, IntPtr is not a constant, code tailoring at the JIT level that currently is not even fathomable :). For a good example of abusing the JIT ability to optimize code based on constant expressions at runtime see the Microsoft Bond code. That source is a huge repository of all the weird things you can do when you have that (currently it is so difficult to follow that it is practically of no use). |
I am not familiar with C++. Hence maybe I have completely missed the topic. Can compiler be written smart enough to know that some entity is constant without explicitly writing "constexpr"? Is it possible to make a rule/algorithm/check that returns true or false if some entity in program is constant? Probably would be fine to evaluate expressions at compile time, but could we do that (theoretically) without "constexpr". |
@redknightlois oh, now I get what you're saying I thought that you somehow imagined a world where the C# compiler actually runs the program and somehow pulls values out of it. :) |
@gordanr I don't think it's possible for the same reason we need to mark variables with
There are probably more things that can go wrong. p.s. As for the keyword maybe we can use |
Hi All,
|
You're right but it's not only about that it will increase the time it takes to compile but it will also may change the behaviour of the program so it must be an opt-in feature.
That's the idea . :)
In my mind, caching and constants are two different things, how do you propose this to work? also, how do you imagine const and non-const to mix? I mean if the arguments are non-const then the function would return different result each time so what would you cache? can you elaborate on this? :) |
Hi Eyal, For example:
When this code gets compiled, I would expect to see something like:
You'll see that in this scenario, this lets me embed values into the When having a "const function", I really think that should be shorthand for
Also, one other thing... someone on an earlier thread talked about reducing
Should all get reduced down into one line:
On Fri, Nov 11, 2016 at 9:06 AM, Eyal Solnik notifications@github.com
Tony Valenti |
@TonyValenti Yeah, it would be great if the JIT would take advantage over this feature and do what the compiler can't do at compile time for farther optimization. |
It looks like that "constexpr" acts as a hint, like a register keyword in C. @eyalsk I think that analogy with mark variables with const, you mentioned earlier, is bad. We must mark variable with const to ensure variable not to be changed. There is no proof that we must mark "constexpr" to function if we want to get value at compile time. Possibly that can mark compiler. What happens when someone mark entity with "constexpr" when we are completely sure that entity isn't constant. Should compiler warn or throw compile time error? If the compiler warns or throws error, that means there is some algorithm to decide that entity isn't constant. If we have that algorithm, why should we manually mark something? I suppose that there could exist algorithm to decide if something is const. Something probably complex and slow (like Hindley-Milner Type-inference algorithm).
or more possible
|
@CyrusNajmabadi Many of these things can be achieved with code generators the only important issue is how code generators is going to be integrated into the language? if it's through attributes then it's obviously impose some limitations as I pointed out above, if someone on the language team can address this question then I think that at least for my needs that would be sufficient. p.s. @asdfgasdfsafgsdfa Maybe this is what you meant when you talked about reflection What Are Macros Good For? --Scala However, this seems like a compile-time kind of reflection whereas in .NET it's more of a run-time thing. |
@asdfgasdfsafgsdfa - Anyways, Here's an example of something that came up today that would be a really nice use-case for this feature. I have an app that contains an embedded license file to use a third-party library. The license file has an expiration date in it and every so often we have to update the license file. I want to build a class that will read the embedded resource and let me access certain properties on it. Ideally, it would be a compile time error if the license file was within a certain range of its expiration. While I can (and did) build a class that can do all that at run time, if I had |
@TonyValenti And you can't do that as part of your build process? it must be part of the language? |
You misunderstood; I was making the same point that you were making. It would be hard if Roslyn had to figure out what functions to exclude, and it would be useless: therefore, Roslyn should not care what functions you use. |
I think this is what @eyalsk has been trying to say for a while now. And what I tried to support. The value of the idea is being lost by expanding the scope too much.
One of the examples used a few times is a real limitation in the language - Attribute parameter values. Since they want const or similar "defined as known values" arguments, not just "known values", or "knowable values".
will not work. But, v is "knowable" as a const with the available information.
would work under the basic idea. A good solution today is better than a perfect solution tomorrow. I proposed the scope as - "The language shall provide the user with the ability to assign the results of the evaluation of any construct (object or expression), that can be evaluated to "values", at compile time, using the information available within the assembly, into a const." You can hand the compiler anything, but if it can't evaluate it from the info in scope it errors. Assembly is the scope I picked, because that seems kind-of like a "this" or "native scope". |
From an implementation perspective, a most basic approach could be an "intellisense assertion". The constexpr tells intellisense "don't check if this is a const, the compiler will". Within the compiler, evaluation of constexpr occurs early in the chain. Here again, a very simple approach could still provide benefit now. In the attribute example above, a "fist-pass" wouldn't even need to do a value check, a type check would provide a short-cut path. |
@CyrusNajmabadi
Alright. There are actually quite heavy limitations, to make it short:
You could say that about simpler examples as well, right? Is evaluating Pow(2,3) easier? Or do you suggest only a few functions should be supported? Like Math.* and maybe some String.* methods? And how is it decided what methods get to be in the allowed-set? |
Interesting, so this would be an feature exclusively to allow for "better code". But it wouldn't work for attributes that want a string in their constructor, right? |
I disagree. Compile should not be limited to only values that can be const.
that would be an artificial limitation that is not necessary and
significantly reduces its usefulness.
On Mon, Jan 30, 2017 at 7:41 AM asdfgasdfsafgsdfa ***@***.***> wrote:
@CyrusNajmabadi <https://github.com/CyrusNajmabadi>
Well *if* code generators allow us to replace a property completely (how
get, set, and initializer are implemented) then the whole point is moot and
the feature is useless. Because then we can simply use them to execute our
code and generate new code to be compiled... (which was the idea with
compile in the first place)
@eyalsk <https://github.com/eyalsk>
Trust me I'm fair! and no I didn't know what you mean, I don't say things
to tease people, I say them because I don't understand when I think that
making yourself clear is very important.
Alright. There are actually quite heavy limitations, to make it short:
1. compile would only be able to return things that would be accepted
by todays const.
2. It can only take expressions that either have no parameters in any
form, or the parameters are given as constants. (no catching variables in
lambdas, ...)
And you can't do that as part of your build process? it must be part of
the language?
You could say that about simpler examples as well, right?
I mean what would be the advantage of having Pow(2,3) or fib(...)
calculated at compiletime?
And wouldn't the answer to those questions not apply to more complex
things as well?
Geniously curious where you'd draw the line and why.
Is evaluating Pow(2,3) easier? Or do you suggest only a few functions
should be supported? Like Math.* and maybe some String.* methods? And how
is it decided what methods get to be in the allowed-set?
Then again we're going in circles as the next logical conclusion would be
some kind of "provable pure method" detector or something... I don't even
know anymore where this is going :P
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#15079 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AM-qVm_II-Udl0Prd63RQmPWmXHfweA6ks5rXeiagaJpZM4KsDsm>
.
--
Tony Valenti
|
@TonyValenti, @asdfgasdfsafgsdfa
It's not about drawing lines, it's about building stairs, starting from the most simple and yet useful cases to the more complex cases. However, I really think that you guys speak about some sort of code generation + macros whereas I speak mainly about calculation. Now, I don't have issues using the source generators feature in order to perform calculations like so:
There are two downsides to this approach a minor and major one:
So really the only concern I have is how source generators are integrated into the language.
No, I'm not going in circles, I was extremely clear about it! the people that are going in circles are the one that don't read. I proposed to add the Methods that are marked with I also think and wrote that more types should be supported by However, I can live with the source generator approach and maybe in the future I'd implement a prototype and then write a proposal for this myself because I have a plan! :) BTW, this discussion helped me immensely! |
@asdfgasdfsafgsdfa while the "better code" aspect is a plus, I'm thinking of something of something like a bounded-constant or conditional-constant (as in, "under these conditions"). If I define a set of conditions where under those conditions y=F(x) will always evaluate to the same value for y, y becomes indistinguishable from a const (within that scope), and should be able to be treated as a const by the compiler. (or more simply, by intellisense, and confirmed by the compiler) With regard to strings I'll include
Let's say functions like
I want to be able to declare passing a Type Enum here, but I can't because it is not "const". The thing is though, within this context, Foo could be ~viewed as By introducing capability to flag the cases we care about, we limit that. One way would be something like
Though that would address the intent, it is confusing and just weird. People might say, "That's redundant". It is, and that's part of the point. This capability is still pretty "safe", in that the compiler should have little trouble verifying that this is a const within this scope. And though it is still narrow, it would address my needs. (With a different syntax.) It solves a known issue, and provides value. Getting to strings.
Realistically, correct. Conceptually, it could, but difficult practically (as the discussions lead me), and opens up a can of worms if you try to take it too far. You could extend this to create "pseudo-constants". Basically, you tell the compiler something is a const and cross your fingers. This is "unsafe". In some cases this might be OK and be worthwhile, if your bounding is good.
Once Foo is initialized Bar is ~indistinguishable from a const, and could be included. I don't know what cost/benefit there is in supporting things like this though. A simple set of capabilities in this topic area provides real value, it doesn't need to be some far-reaching feature with "global" scope. |
I want duck-constants. If it walks like..., talks like... I want to use it I'm my duck soup recipe. |
What do you mean here? I probably don't understand you but the elements of the |
And when you change
So just make it if you can.
Firsly, I prefer And I agree with
|
@Pzixel, yeah, I was arguing against. Conceptually you could, technically very hard, limited value, so very little cost/benefit. Glad it was already destroyed so we don't have to again. @eyalsk It's the Enum type I can't use as a parameter in my declaration. I have to use object and then cast/convert.
|
I don't know what "const function returns one single value by definition" means and yes inlining is part of this I mean after all we're assigning the result to a constant but anyway I don't mind calling it |
@RandyBuchholz Yeah, got you. :) |
@eyalsk https://en.wikipedia.org/wiki/Constant_function . You are talking about pure function calling it const for some reason. Why not just use proper name? |
@Pzixel Just because in one field the term constant function has defined meaning doesn't mean that in another field the same term would mean the same thing. GCC defines constant functions as special case of pure functions:
Besides, I already pointed out that I don't have any objection so I'm not sure what you want me to say... 😄 |
@eyalsk very strange definition, because it can be inferred from So I think we can distinct pure functions (which are called Finally, I really think we shouldn't argue about feature which is even not planned for implementation so we can stop here until its internals are done. I think we both are thinking about some keyword, which allows compiler to check two things: that function doesn't read non-constant memory outside function and it uses others pure functions only, and probably uses this information to perform some compile-time calculations. One of advantages is possible usages in attributes, but there are multiple others. On the other side, I think we both are thinking that implementing |
@Pzixel Sure but I'm not arguing, I'm just trying to explain to you what I meant by constant function because you thought I was speaking about it in the context of mathematics and I wasn't. It's not really strange definition because it is not the same field, Computer Science and Mathematics are two different fields and the GCC designers can call it however they want but anyway I'll call them |
@eyalsk: Halting Problem (capital H, capital P), not halting problem. And what is the functional difference between a program that terminates in 5 years vs one that hangs and never terminates? Because if you are executing my code, and allow loops, I guarantee that I can make it exectue for a lot longer than your next reboot, maybe even without loops. I don't mean that in snarky manner either -- people do those kinds of things simply to find out what happens. Let it happen when it happens, whether it is a genuine error or someone testing boundaries, or an actual use case which hasn't been thought of. Don't think "let's keep it from doing x". |
Sorry missed the fact that it's a term.
|
@eyalsk: The Halting Problem says that it is impossible to write a program, that taking another program as input, will always be able to determine that it halts. You would have to limit what the second program can do, in order to do that. Specifically, no loops or branches. As for the program taking 5 years to complete and that being what it takes -- that is exactly my point. From a practical point of view, the compilation will either finish before the server is rebooted, or it won't. If it doesn't finish before it gets wiped out by a reboot, it doesn't matter that it would have finished with a bit more time or not. Assume that the developer is ok with however long it takes. And if it takes user input to finish, then assume that the programmer knows that and and has taken steps to provide it. Don't try to second guess what he wants. Maybe he never intends to compile it on a build server, maybe he has scripted the system so that "user" input is provided. |
@jramsay Never mind, I confused halting with blocking, now some of the things you said make more sense to me. :) p.s. I think that we were discussing too many ideas for a single post so I'm closing it for now. At some point in the future I'll probably create separate issues about pure functions and constants types but before that I'll do my homework pretty good. |
One of the features that I like from C++11/14 is
constexpr
that stands for constant expression and the reason is it allows to evaluate expressions at compile-time.So here are just some questions to open the discussion:
Do you think that something like
constexpr
has a place in C#?Do you think it should behave exactly as it does in C++? if not what needs changing? what's your thoughts? do you have an alternative?
Do you need something like this today for your current C# projects?
My opinion:
At first when I wrote this post I had thoughts about allowing
Math
to be evaluated at compile-time when feasible, I already proposed to add the power operator ** into the language and I thought that if we would have something likeconstexpr
in C# we could write these operators ourselves.After some thoughts and quite a bit of discussions here about the cons and pros of this feature which goes up to performance benefits, I do think it has a value but probably a lot less value when we consider how it works in C++ and the set of problems it solves there (which aren't the same problems that exist in C#).
Please read my comment which is the gist of the discussion and my conclusions.
The text was updated successfully, but these errors were encountered: