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

[Umbrella] How would you imagine constexpr in C#? #15079

Closed
iam3yal opened this issue Nov 8, 2016 · 225 comments
Closed

[Umbrella] How would you imagine constexpr in C#? #15079

iam3yal opened this issue Nov 8, 2016 · 225 comments

Comments

@iam3yal
Copy link

iam3yal commented Nov 8, 2016

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:

  1. Do you think that something like constexpr has a place in C#?

  2. 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?

  3. 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 like constexpr 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.

@Unknown6656
Copy link

Unknown6656 commented Nov 8, 2016

Take a look at these following issues. This feature is also (partly) mentioned in them.
#12238
#14665
#10506
#11259
#9627
https://github.com/dotnet/coreclr/issues/3633

@AdamSpeight2008
Copy link
Contributor

If we are seeing multiple requests for some form of Const<Expr<T>>, we should be considering why?

@vbcodec
Copy link

vbcodec commented Nov 8, 2016

@eyalsk

but I love math

So, what result is from 0 / 0 ?

@iam3yal
Copy link
Author

iam3yal commented Nov 8, 2016

@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. :)

@AdamSpeight2008
Copy link
Contributor

@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.

@iam3yal
Copy link
Author

iam3yal commented Nov 8, 2016

@AdamSpeight2008 oh sorry, I thought you're asking why we need it. :)

@AdamSpeight2008
Copy link
Contributor

@eyalsk One issue I see with it is the example you reference uses templates. Touchy Subject

@iam3yal
Copy link
Author

iam3yal commented Nov 8, 2016

@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. :)

@AdamSpeight2008
Copy link
Contributor

Templates and Macros have previously been brought up and respectively declined by the language design teams. #14619

@iam3yal
Copy link
Author

iam3yal commented Nov 8, 2016

@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 constexpr suspport.

@Unknown6656
Copy link

Unknown6656 commented Nov 9, 2016

@eyalsk

@Unknown6656 Yeah, this is the reason I created this post to discuss all of these and maybe come up with a generalized solution. :)

Oh, perfect!
Then I have two things:

  1. How would we define constant (I asked this previously here: Proposal: Compile time expressions  #12238 (comment) and here: Proposal: Compile time expressions  #12238 (comment))?

    Should everything, which can be computed determinisitcally, be computed during compilation?

    Should we add some kind of compiler-hint?
  2. maybe rename the question to contain the tag [umbrella] (but that is not important 😉)

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.
e.g. The calculation of the Fibonacci sequence is deterministic, as it is clearly defined and one gets the same mathematically proven result with the same computation and same parameters. Should it therefore be pre-computed by the compiler?

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?

@iam3yal iam3yal changed the title How would you imagine constexpr in C#? [Umbrella] How would you imagine constexpr in C#? Nov 9, 2016
@iam3yal
Copy link
Author

iam3yal commented Nov 9, 2016

@Unknown6656

How would we define constant (I asked this previously here: #12238 (comment) and here: #12238 (comment))?

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.

Should everything, which can be computed determinisitcally, be computed during compilation?

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.

  1. maybe rename the question to contain the tag [umbrella](but that is not important 😉)

Done! 😉

The calculation of the Fibonacci sequence is deterministic, as it is clearly defined and one gets the same mathematically proven result with the same computation and same parameters. Should it therefore be pre-computed by the compiler?

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.

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, before we draw the line we need to define the rules. 😄

@Unknown6656
Copy link

Unknown6656 commented Nov 10, 2016

@eyalsk:

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.

I think the pre-processor should build a caller tree to sort the methods by complexity and recursion depth.
It should then optimize the less complex methods first, and then move onto the more complex ones. I have no idea, which should be the complexity "limit", but it will be dependent from the method size.

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.

I think that we should have some kind of compiler hint or switch, like: csc /target:library /out:foobar.dll /max-total-optimization-duration:500ms *.cs or a compiler-intrinsic attribue:

[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....

@iam3yal
Copy link
Author

iam3yal commented Nov 10, 2016

@Unknown6656

I think the pre-processor should build a caller tree to sort the methods by complexity and recursion depth.
It should then optimize the less complex methods first, and then move onto the more complex ones. I have no idea, which should be the complexity "limit", but it will be dependent from the method size.

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 we should have some kind of compiler hint or switch, like: csc /target:library /out:foobar.dll /max-total-optimization-duration:500ms *.cs or a compiler-intrinsic attribue:

I think that it's better to set the recursion depth as opposed to time limit like in Clang

@Unknown6656
Copy link

@eyalsk

Why it needs to be sorted? :)
I guess what I'm asking is if you sort it how does this helps optimization?

I think it would be better to optimize all "small" methods first and complex methods last, if you have an optimization time limit.

I think that it's better to set the recursion depth as opposed to time limit like in Clang

Agreed. This would invalidate my point of sorting the methods first by complexity.

@redknightlois
Copy link

@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.

@iam3yal
Copy link
Author

iam3yal commented Nov 10, 2016

@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?

@redknightlois
Copy link

@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:

public class IntPtr
{
       [JitConstant]
       public int Size { get ; }
}

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).

@gordanr
Copy link

gordanr commented Nov 10, 2016

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".

@iam3yal
Copy link
Author

iam3yal commented Nov 10, 2016

@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. :)

@iam3yal
Copy link
Author

iam3yal commented Nov 10, 2016

@gordanr I don't think it's possible for the same reason we need to mark variables with const today, the compiler cannot assume we want the function to be evaluated at compile time, beyond that there's some implications, here are some of them:

  1. Behaviour of existing code may change so this is a breaking change.
  2. If the assumption is such that all functions can be evaluated at compile time then compilation times will increase, greatly.
  3. If we wouldn't have to mark functions with constexpr or any other keyword then we wouldn't be able to make it safe because there wouldn't be rules that differentiate between non-constexpr functions and constexpr functions.
  4. There's probably no reliable way to determine that, not something that I'm aware of at least so you likely to have false-positives.

There are probably more things that can go wrong.

p.s. As for the keyword maybe we can use const in C# instead of constexpr but maybe there's more plans for const like immutability, dunno. :)

@TonyValenti
Copy link

Hi All,
I wanted to toss in my two cents here and say that I really like the idea of being able to push more work into the compiler.

  1. Someone on here mentioned that compile times would increase. While that is true, decreasing run times has more value to end users. I think that, ideally, things should be "more pre-evaluated" as I go from Debug to Release. Who cares if my build server takes 1 hour to do a compile if it saves my users time?
  2. I think that only pure functions should be able to be const.
  3. Possibly related, I know that I often write wrapper code around certain functions to basically do a permanent runtime caching of their value. I think that it would be really nice if a const function is called with non-const parameters would essentially essentially cache the result.

@iam3yal
Copy link
Author

iam3yal commented Nov 11, 2016

@TonyValenti

Someone on here mentioned that compile times would increase. While that is true, decreasing run times has more value to end users. I think that, ideally, things should be "more pre-evaluated" as I go from Debug to Release. Who cares if my build server takes 1 hour to do a compile if it saves my users time?

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.

I think that only pure functions should be able to be const.

That's the idea . :)

Possibly related, I know that I often write wrapper code around certain functions to basically do a permanent runtime caching of their value. I think that it would be really nice if a const function is called with non-const parameters would essentially essentially cache the result.

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? :)

@TonyValenti
Copy link

Hi Eyal,
As I've thought about it, I think you're right about caching and constants
being two different things, however, I was thinking that there might be
plenty of use cases where we want non-pure consts as well.

For example:

var BuildDate = const DateTime.Now;
var BuildUser =
const System.Security.Principal.WindowsIdentity.GetCurrent().Name;
var BuildMachine = const System.Environment.MachineName;


Console.WriteLine("This app was built on {0} by {1} on {2}", BuildDate,
BuildUser, BuildMachine);

When this code gets compiled, I would expect to see something like:

var BuildDate = new DateTime(2016,11,11,.....);
var BuildUser = "TonyValenti";
var BuildMachine = "TonyPC;

You'll see that in this scenario, this lets me embed values into the
assembly at runtime.

When having a "const function", I really think that should be shorthand for
saying "Everywhere this function is called, if possible, reduce it to a
constant".

public static const int Fibonacci(int n) {
int a = 0;
int b = 1;
// In N steps compute Fibonacci sequence iteratively.
for (int i = 0; i < n; i++)
{
   int temp = a;
   a = b;
   b = temp + b;
}
return a;
}

public static void Main(){
  Console.WriteLine("Please Enter a Number:")
  var N = int.Parse(Console.ReadLine());
  //This call does not get reduced because Fibonacci(N) is not known at
compile time..
  var FibN = Fibonacci(N);

  var x = 10;
  var Fib10 = Fibonacci(x);
  //This call would get reduced into:
  var Fib10 = 55;
}

Also, one other thing... someone on an earlier thread talked about reducing
"Known at compile time" values into constants/literals. I think this would
be a good thing to look at as well because of how it would transform
certain code. For example, theoretically, this code:

var BuildDate = const DateTime.Now;
var BuildUser =
const System.Security.Principal.WindowsIdentity.GetCurrent().Name;
var BuildMachine = const System.Environment.MachineName;

Console.WriteLine("This app was built on {0} by {1} on {2}", BuildDate,
BuildUser, BuildMachine);

Should all get reduced down into one line:

Console.WriteLine("This app was built on January 1, 2016 by TonyValenti on
TonyPC");

On Fri, Nov 11, 2016 at 9:06 AM, Eyal Solnik notifications@github.com
wrote:

@TonyValenti https://github.com/TonyValenti

Someone on here mentioned that compile times would increase. While that is
true, decreasing run times has more value to end users. I think that,
ideally, things should be "more pre-evaluated" as I go from Debug to
Release. Who cares if my build server takes 1 hour to do a compile if it
saves my users time?

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.

I think that only pure functions should be able to be const.

That's the idea . :)

Possibly related, I know that I often write wrapper code around certain
functions to basically do a permanent runtime caching of their value. I
think that it would be really nice if a const function is called with
non-const parameters would essentially essentially cache the result.

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? :)


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-qVs2xic46L7nfbp9OCXnwTAhSQ_3vks5q9ISDgaJpZM4KsDsm
.

Tony Valenti

@iam3yal
Copy link
Author

iam3yal commented Nov 11, 2016

@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.

@gordanr
Copy link

gordanr commented Nov 11, 2016

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).
Algorithm could return something like

  1. Completely sure that entity is constant.
  2. Completely sure that entity is not constant.

or more possible

  1. Completely sure that entity is constant.
  2. Completely sure that entity is not constant.
  3. Can't decide.
  4. Can't decide. Entity too complex. Analysis aborted after 20ms.
  5. ...

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@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.

@TonyValenti
Copy link

@asdfgasdfsafgsdfa -
I tried to like your post but github wouldn't let me.

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 compile(), it would allow me to discover those values at compile time. The performance boost would be negligible (how long does it really take to deserialize a 2kb XML file?) but the development experience would be so much nicer because I would know, at compile time, whether my XML file would deserialize or not.

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@TonyValenti And you can't do that as part of your build process? it must be part of the language?

@jnm2
Copy link
Contributor

jnm2 commented Jan 30, 2017

@asdfgasdfsafgsdfa

@jnm2

The hard part would be if Roslyn had to figure out what functions to exclude, thereby making Console.ReadLine a compile error.

What? Why??
If the programmer says "evaluate Console.ReadLine()" at compile time, then the compiler should do it.

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.

@RandyBuchholz
Copy link

@magol , but I this discussion has become a bit too unfocused
@CyrusNajmabadi This is an unbelievably massive feature request that ends up realistically saving extremely little

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.

Since we are really dealing with special case scenarios, working towards (overly) generalized solutions is going in the wrong direction.

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".

const int x = 5;
const int y = 6;
var v = x + y;

[attrib(v)]
class Foo{}

will not work. But, v is "knowable" as a const with the available information.

const int x = 5;
const int y = 6;
constexpr v = x + y;

[attrib(v)]
class Foo{}

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".

@RandyBuchholz
Copy link

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. const + const == const.

@asdfgasdfsafgsdfa
Copy link

@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

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

@asdfgasdfsafgsdfa
Copy link

asdfgasdfsafgsdfa commented Jan 30, 2017

@RandyBuchholz

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".

Interesting, so this would be an feature exclusively to allow for "better code".
The idea is being able to give constants (or constants derived from other constants though some expression) better names, and maintain the representation in the code how they were created (because rarely do people recognize 1.570... but they do recognize PI/2). Did I understand the intention correctly?

But it wouldn't work for attributes that want a string in their constructor, right?

@TonyValenti
Copy link

TonyValenti commented Jan 30, 2017 via email

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@TonyValenti, @asdfgasdfsafgsdfa

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.3

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:

[Calculator(Method.Fib, 40)]
const int fib = 0;

[Calculator(Method.CubeVolume, 2)]
const int cube = 0;

There are two downsides to this approach a minor and major one:

  1. Major: Attributes are limited to constants and constants themselves are limited to a set of built-in types.

  2. Minor: It's not as expressive as it can be.

So really the only concern I have is how source generators are integrated into the language.

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

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 const modifier to methods that can be evaluated at compile-time and have specific rules around it.

Methods that are marked with cost can still be evaluated at run-time if either one of their arguments isn't a constant.

I also think and wrote that more types should be supported by const.

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!

@RandyBuchholz
Copy link

RandyBuchholz commented Jan 30, 2017

@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

@TonyValenti 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.

Let's say functions like const + const are "true" const functions. These are easy, and are essentially self-bounding, and "safe". But, as @TonyValenti says, the cases become pretty narrow and limiting. Taking a step up, I'll use Attributes again, because that is my really investment in this feature.

public enum Foo { One, Two };
public enum Bar { One, Two };

[Attribute([Enum not enum])]
public class Worker{}

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 const int[] Foo { 0, 1 }. We could expect the compiler to know this, but that gets to be a big expectation for it to try to evaluate everything.

By introducing capability to flag the cases we care about, we limit that. One way would be something like

public const enum Foo { One, Two };

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.

@asdfgasdfsafgsdfa But it wouldn't work for attributes that want a string in their constructor, right?

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.

public class Foo{
    public readonly string Bar;
    public Foo(){
       Bar = "Constructed";
    }
}

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.

@RandyBuchholz
Copy link

RandyBuchholz commented Jan 30, 2017

I want duck-constants. If it walks like..., talks like... I want to use it I'm my duck soup recipe.

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@RandyBuchholz

I want to be able to declare passing a Type Enum here, but I can't because it is not "const".

What do you mean here? I probably don't understand you but the elements of the enum are basically constants and you can pass the type in the form of typeof(SomeEnum).

@Pzixel
Copy link

Pzixel commented Jan 30, 2017

@asdfgasdfsafgsdfa

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 when you change 2 to 3 you are doing what? Because the main reason of Pow(2,3) when it's Pow(MY_SHINY_BOX_SIDE_LENGTH, 3) and side is const. In this case you are forced to insert comments like CHANGE THIS VALUE WHEN YOU ARE CHANGING "MY_SHINY_BOX_SIDE_LENGTH" or just do not use const. In some cases it can be replaced with static readonly and we don't care about single calculation at runtime, but sometimes we need it as an attribute value... And here it is: our ugly comment-driven developement way.

@TonyValenti

I disagree. Compile should not be limited to only values that can be const.
Some intelligent guy said that

Basically you want a super smart compiler than can understand what the rather complicated serializer code is doing and simplify it. The main problem is that nobody figured out how to write such a super smart compiler. Any takers?

So just make it if you can.

@eyalsk

I proposed to add the const modifier to methods that can be evaluated at compile-time and have specific rules around it.

Methods that are marked with cost can still be evaluated at run-time if either one of their arguments isn't a constant.

I also think and wrote that more types should be supported by const.

Firsly, I prefer pure because const function returns one single value by definition. pure describes intention of function, const describes that you want to inline result somewhere. Function call site is not its area of responsability so it shouldn't assume if it will be constant or not. It just saying okay, my value always is the same for this parameters, do everything you want with this knowledge. Nothing about constness. Call site can decide whenever he want to inline function value or calculate at runtime (for example, if we have one parameter supplied at runtime).

And I agree with expanded set of const types, because today's soltution with decimals, arrays in attributes only and so on is really very ugly.

@RandyBuchholz

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.
Somewhere here or in linked posts this idea was completly destroyed. It's very hard internally to do such optimization thus compiler doesn't.

@RandyBuchholz
Copy link

RandyBuchholz commented Jan 30, 2017

@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.

public class ProcessAttribute : Attribute{
        public ProcessAttribute(object route) {

can't do
       public ProcessAttribute(Enum route){

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@Pzixel

Firsly, I prefer pure because const function returns one single value by definition. pure describes intention of function, const describes that you want to inline result somewhere. Function call site is not its area of responsability so it shouldn't assume if it will be constant or not. It just saying okay, my value always is the same for this parameters, do everything you want with this knowledge. Nothing about constness. Call site can decide whenever he want to inline function value or calculate at runtime (for example, if we have one parameter supplied at runtime).

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 pure.

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@RandyBuchholz Yeah, got you. :)

@Pzixel
Copy link

Pzixel commented Jan 30, 2017

@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?

@iam3yal
Copy link
Author

iam3yal commented Jan 30, 2017

@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:

A special case of pure functions is constant functions. A pure function that does not access global memory, but only its parameters, is called a constant function. This is because the function, being unrelated to the state of global memory, will always return the same value when given the same parameters. The return value is thus derived directly and exclusively from the values of the parameters given.

Besides, I already pointed out that I don't have any objection so I'm not sure what you want me to say... 😄

@Pzixel
Copy link

Pzixel commented Jan 30, 2017

@eyalsk very strange definition, because it can be inferred from pure function definition. If function doesn't have any hidden state, it cannot reference global memory locations which is not constants, but if they are, they can be inlined thus there is absolutely no difference if we don't care (true) about if constant is inlined or referenced.

So I think we can distinct pure functions (which are called constant in gcc documentation) and some subset of pure functions (which are called pure in gcc documentation). I think we should take more common name, used accross multiple languages: D/Lisp/Haskell/whatever instead of one, used in one compiler of C++.

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 everything in compile time is almost unsolvable today, so we should start with little feature described above and then move toward more generic solution.

@iam3yal
Copy link
Author

iam3yal commented Jan 31, 2017

@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 pure functions from now on and stick to with it.

@jrmoreno1
Copy link
Contributor

@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".

@iam3yal
Copy link
Author

iam3yal commented Feb 1, 2017

@jrmoreno1

Halting Problem

Sorry missed the fact that it's a term.

And what is the functional difference between a program that terminates in 5 years vs one that hangs and never terminates?

I didn't say that it's not fine for a program to halt get blocked if that's the intention of the programmer so be it but I don't think that a compiler should halt, let alone getting blocked by a program that it compiles, however, if a project takes 5 years to compile and that's what it takes then that's what it takes.

It's like designing and building a drilling rig that halts and when customers would ask the company why? they would tell them it's a feature whereas such machine should never halt but make a progress.

@jrmoreno1
Copy link
Contributor

@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.

@iam3yal
Copy link
Author

iam3yal commented Feb 11, 2017

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests