Replies: 58 comments 3 replies
-
A lot of related discussions are also here: dotnet/roslyn#15079 |
Beta Was this translation helpful? Give feedback.
-
It would be great if JIT and .NET Native would optimize pure function having knowledge of their "purity". |
Beta Was this translation helpful? Give feedback.
-
It appears that with the Rust language that the new const keyword allows specifying behavior to what is being discussed here for C#. With Rust it looks like this also signals to the compiler cases where certain calculations can be done at compile time instead of run time which reminds me of how literal string concatenation is handled in C#. ex. const fn foo(x: i32) -> i32 {
x + 1
} |
Beta Was this translation helpful? Give feedback.
-
I believe it's useful to declare, as an interface contract, that a method won't affect an object. Consider the below: // <summary>Tabulate votes</summary>
public interface ITabulator
{
/// <summary>
/// True if tabulation is complete; else false.
/// </summary>
bool Complete { get; }
/// <summary>
/// Perform the next round of tabulation.
/// </summary>
void TabulateRound();
/// <summary>
/// Get the winners and losers for this round next round of tabulation.
/// </summary>
/// <returns>The state of those Candidates who have become winners and losers
/// in this round of tabulation. Returns an empty dictionary when tabulation is complete.</returns>
Dictionary<Candidate, CandidateState.States> GetTabulation();
/// <summary>
/// Get winners and losers at the current round, with vote counts.
/// </summary>
/// <returns>A full set of candidate states and votes.</returns>
Dictionary<Candidate, CandidateState> GetFullTabulation();
} Let's say
It seems to me a special keyword to mark these as non-state-altering—a contract to restrict implementations—would make sense. Let's say What does that mean to the implementer?
In other words: I want to specify, through code contract, that these calls won't change anything. Now, implementing this is horrendous. If you apply it retroactively, you have a bunch of assemblies to update. The new .Net and .Net Core libraries will then require certain methods in derivative classes to be Likewise, any methods which are already functionally In other words: a That risk of breakage is inherent in the exercise, since core libraries would require updating to declare what is |
Beta Was this translation helpful? Give feedback.
-
Why do we even need an attribute or keyword? If the compiler can see that a method is "pure" then that's fine, just tag the method in some way (invisible to the developer). Then any use of the method when referenced at compile time can be policed. A method can be deemed pure if:
So rather than policing the method code itself, just determine if it is or is not pure and then police references to the method in other code. e.g. public const long DMA_FACTOR = StaticClass.StaticMethod(2, 4); That would be legal if StaticClass.StaticMethod is deemed "pure" and illegal if it is not so it is here that diagnostics would be issued not in the method code itself. |
Beta Was this translation helpful? Give feedback.
-
Because purity is a cross version contract. I could write a simple function that is pure in v1.0 of my library, but add a minor feature that breaks that parity in v1.1, in which case assumptions the C# compiler made via "auto-determination" would be wrong. This is very similar to the new |
Beta Was this translation helpful? Give feedback.
-
What if I write a method which happens to be pure. Then a developer uses my method in a location where only pure methods can be used. I then change my method to be non-pure, and everything breaks. Note that since purity of a function is defined recursively, changing my method to be non-pure might make a method 10 call stacks up stop being considered pure. |
Beta Was this translation helpful? Give feedback.
-
Purity is, generally speaking, also allowed on instance members. |
Beta Was this translation helpful? Give feedback.
-
True but we;d get a runtime failure when the consuming code (expecting pure) discovers that the method actually isn't. But yes, in this case I do see the need for an attribute/keyword. |
Beta Was this translation helpful? Give feedback.
-
Yes I see now, however this kind of scenario exists today. consider a library assembly v1.0 that exposes this: public static class Library
{
public const long PURE = 1234;
} Then consider version 1.1 of that library is leveraged but it now exposes it like this: public static class Library
{
public static long PURE = 1234;
} This consumer reference would fail to compile. against v 1.1 but would be fine against v 1.0 class Program
{
public const long scale = Library.PURE;
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
} |
Beta Was this translation helpful? Give feedback.
-
Why? by definition allowing that anticipates some dependency on instance state which surely is undesirable? |
Beta Was this translation helpful? Give feedback.
-
@Korporal Okay, let's consider this function class Config {
public int GetMaxConnectionCount() => 5
} Is it pure? Can my library users use it in their const int Foo = new Config().GetMaxConnectionCount() ? |
Beta Was this translation helpful? Give feedback.
-
But, without an attribute or similar, how could the developer indicate that they want their method to be pure and that they want the compiler to error should the programmer inadvertently call an impure function? If everything's implicit then the feature kind of has no value, as every function could just switch from impure to pure and back again with no code changes. |
Beta Was this translation helpful? Give feedback.
-
It's not pure (referentially transparent) according to my criteria above, the class is not static and the method is not static and could therefore be overridden or hidden potentially. |
Beta Was this translation helpful? Give feedback.
-
My example here has no attribute and the compiler fully detects the situation - this is code we can write today. |
Beta Was this translation helpful? Give feedback.
-
It depends. Often, with some concepts there is a belief that the feature will not be valuable enough if it is too boxed off. You want the new feature to be much more generally applicable to raise the value to the point that it's worth doing. Now, there may be an argument to start small to "test the waters" so to speak. And, if that works well, you can then expand things out as long as your original designs didn't limit you too much in that manner. |
Beta Was this translation helpful? Give feedback.
-
You guys may well be right, I'm not opposed really my position arose from not seeing a real need for instance level support, I can see now the need is not technical but is deemed helpful from the point of view of the language as a whole. So, is there any chance we'll ever see this? What are the main hurdles at this point in time? |
Beta Was this translation helpful? Give feedback.
-
An attribute/keyword is still needed for You also have to be able to recognize |
Beta Was this translation helpful? Give feedback.
-
I believe so yes. But it will require enough support and value to get critical mass to move forward.
A backlog of hundreds of viable and interesting features for the language and platform :) |
Beta Was this translation helpful? Give feedback.
-
Well I disagree, the compiler can infer purity I think in a static/static setting and internally regard the method as pure and mark it as such, this was part of the reason for my emphasis of static/static it eliminates (at least I think it does) the need for any new keywords.
|
Beta Was this translation helpful? Give feedback.
-
To think that all I was hoping to do was: public const int FACTOR = 2 ** 5; |
Beta Was this translation helpful? Give feedback.
-
Right (though it oculd do the same with instance).
Sure. But the problem here is simply that if it's non-annotated, then you could easily break things without realizing it. That was the whole conversation above. |
Beta Was this translation helpful? Give feedback.
-
public const int FACTOR = 1 << 5;
public const int FACTOR = 0b100000; ? |
Beta Was this translation helpful? Give feedback.
-
In addition to what @CyrusNajmabadi said. The real primary hurdle is determining how purity works with regards to reference assemblies. Reference assemblies are today not emitting method bodies. If The compiler then also depends on the runtime/current machine for computing the correct output, which has various questions around cross-platform support and "what if I'm running on some other runtime, etc". I think the latter is likely fine though, as it is really no different than |
Beta Was this translation helpful? Give feedback.
-
You could also perceive of this being an official supported That would limit some functionality (such as explicit const) and still have some runtime (rather than only compile time) cost, but it might also be the right first step. |
Beta Was this translation helpful? Give feedback.
-
Right. There's no doubt this is useful. But there does seem to really be a lack of really useful cases for C# (or, rather, the C# community isn't coming with some killer examples where this could be shown to be amazingly useful**). Now, it may just be that thsi feature will never have that. And that it's more about having a critical mass of little bits of value spread over the entire ecosystem. But if that's the case, it's harder to show that this needs to be done, and it will affect how things are prioritized. Note: i'm not saying that this is not a useful feature. I'm just saying that it may be hard to demonstrate the value and to determine the relative worth compared to other features that can be done. -- ** For example, with |
Beta Was this translation helpful? Give feedback.
-
@tannergooding If this is just pushed to 'runtime' (not AOT), then is there any value? What's the benefit there versus just having normal computation happen at runtime like it works today? |
Beta Was this translation helpful? Give feedback.
-
Without a "trusted" attribute, you have to analyze IL/etc. This can be error prone, may not version well, and may be costly from a JIT time perspective. Having a "trusted" attribute is basically an Note: I have no clue if the runtime team would even be willing to support such a feature, but there are a number of interesting optimizations that could potentially happen otherwise (like plugging into value numbering or JIT time constant folding). |
Beta Was this translation helpful? Give feedback.
-
You could then also opt-into support in some cases that aren't supportable during C# compilation time, such as dealing with native sized numbers. |
Beta Was this translation helpful? Give feedback.
-
No one seems to be mentioning that a pure method would always be thread-safe; whereas one that mutates state is in principle non safe. That would be imo one of the biggest use cases of this feature in C#. At work we just had a bug because of this, and you know how tricky it is to find or test threading bugs. There should be a keyword, and the compiler should fail when it finds non-pure calls in methods annotated as pure. At least for this case it's not enough for the compiler to infer purity and optimize and act in consequence; I want to enforce it to avoid (first of all) threading bugs. Instance methods could be pure according to this definition, if they read only state set in the constructor. But to enforce this C# would also need deep const/readonly-ness, instead of its normal shallow |
Beta Was this translation helpful? Give feedback.
-
@wcrowther commented on Thu Dec 17 2015
Be able to mark a method as having no side effects or external dependencies, ie: it does not change any state outside the inputs or outputs. Any code that attempts to do this would throw an exception. My thought was that the keyword could be "functional", "pure" (as in a "pure functions" mentioned in some Msdn documentation ), "purefunction", or even "nosideffects".
See https://msdn.microsoft.com/en-us/library/bb669139.aspx for some current naming conventions and reasons for this feature.
@sharwell commented on Thu Dec 17 2015
❓ Considering we already have
[Pure]
, which is very short and doesn't require new keywords be added, what additional benefit(s) would this proposal bring?📝 Note that I'm not necessarily against this proposal. I'm just trying to get some more context. 😄
@HaloFour commented on Thu Dec 17 2015
If the compiler were to start enforcing method purity through the addition of new warnings or errors than a new keyword might be necessary in order to avoid breaking changes.
An analyzer that could issue warnings based on
PureAttribute
would probably be a good start, though.@alrz commented on Thu Dec 17 2015
Doesn't
pure
imply "always evaluating the same result value given the same argument value"? I think C++const
would be more familiar; e.g.void M() const { }
. or whatever #159 would use.@orthoxerox commented on Thu Dec 17 2015
I like this idea, but how do we verify that a function has no side-effects, recursively? Does memoizing count as a side-effect? If not, how can that be verified? Even if #49 is implemented so we can encapsulate that
ConcurrentDictionary
instance inside the function, we cannot markGetOrAdd()
as[Pure]
, because it isn't.@alrz commented on Fri Dec 18 2015
@orthoxerox I think it needs an immutable map, and yet map itself is a mutating state, probably needs a state monad or something? Then recursion is off the table, I guess.
@orthoxerox commented on Fri Dec 18 2015
@alrz One option would be to add
Memoized
as a new parameter to[Pure]
. This new parameter would force the compiler to rewrite the function into something like this if the original function was verifiably pure.@leppie commented on Fri Dec 18 2015
Just comment.
If a method returning void is marked as pure, the compiler should be able to remove it as it has no side-effects.
@sharwell commented on Fri Dec 18 2015
@leppie Common cases where you may want code that doesn't affect program state but don't need to return a value:
void
can have a return value of sortsRemoving the following would probably not be desirable, yet it's arguably a pure method:
@leppie commented on Fri Dec 18 2015
@sharwell The presence of a possible throw, hardly makes it 'pure' :) But I get what you saying. Perhaps pure is not the best word here.
@alrz commented on Fri Dec 18 2015
@orthoxerox Memoization wouldn't make it to the language. (already proposed in #5205).
@MgSam commented on Fri Dec 18 2015
The existing
[Pure]
attribute is not particularly helpful, as it doesn't even produce a squiggle if you write to fields. I'm all in favor of a better way to mark methods that shouldn't have side effects. Right now I am often forced to try to use static methods for this purpose, but that only goes so far because sometimes you need static fields and there's nothing to stop you from writing to them.@sharwell commented on Fri Dec 18 2015
This could be implemented as an analyzer. However, it's a bit complicated.
ref
. Writing to a struct parameter withref
can be fine as long as the reference points to a stack-allocated struct in a caller@alrz commented on Fri Dec 18 2015
Does a so-called "pure function" as a sole feature really help in a non-immutable type? C++ allowed this and instead disallowed it for
static
methods. Makes sense that way, but with immutable types I suspect pure functions as a distinct entity would make the world any better. I mean, having partially-immutable types might be confusing, yet, the C++ way of "purity" might be a better approach — purity at the function level and immutability at the variable declaration (type usage) level, instead of type declaration level. This would allow declaring variables like "immutable arrays" e.g.int immutable[] arr = {3,4};
which I think even #159 couldn't address very well (viaimmutable unsafe
).@ashmind commented on Tue Dec 22 2015
Concept of "pure" does not have a single clear definition between languages, so it might be better to use some alternative terminology.
E.g. when I researched this last time, here's what I ended with:
That's not even starting on how exceptions should behave.
I think each limitation we could apply to "pure" has it's own uses, e.g. determinism excludes reading mutable state -- good for concurrency. So maybe some more complex state attribute(s) are needed.
And if we look just at side effects, there is another question -- is this pure?
It can only be verifiably pure if
StringBuilder.Append
is marked with some variant of mutability attribute that specs self-mutation but not outside-mutation. Which again brings the need for more complex mutability descriptions.@alrz commented on Thu Dec 24 2015
@ashmind How about
isolated
forStringBuilder.Append
or the wholeStringBuilder
class?@leppie commented on Thu Dec 24 2015
Local mutation within a method whose variables are not captured (free) would not be impure to me.
@ashmind commented on Thu Dec 24 2015
@alrz
I think at least the following qualities are needed (I'm not suggesting the keywords, just trying to see the whole picture).
this
)new StringBuilder
and any number ofStringBuilder.Append
is side-effect-free and deterministic.That also raises a question of ownership -- let's say we have a class
X
that has internalStringBuilder
in a field. If we can demonstrate that thisStringBuilder
is owned by the class, then we can prove that changing it is changingthis
and not external state. So some kind of[Owns]
annotation would be useful.@alrz commented on Thu Dec 24 2015
@ashmind I didn't understand, having said
isolated
or "CanChangeArguments" methods (only able to change internal state) what is the need for ownership qualifiers? by "internal" we mean "not leaking outside of the class", so they must beprivate
right? I mean aprivate
state doesn't imply it belongs to the enclosing type? and can you please elaborate on "CanReadExternalState" what are its use cases?@ashmind commented on Thu Dec 24 2015
Example:
Is this class changing external state or only state it owns? It's uncertain and depends on whether
inner
is owned by this instance, or whether it might be shared. One example where this is already important is disposal -- e.g. Autofac hasOwned<T>
references that specify that instance is owned and will be disposed by the owner.Reading external state makes function potentially unsafe for threading, and unsafe for permanent memoization. On the other hand, it would mean that function does not change external state, and so is safe to call it automatically in debugging, for example.
@alrz commented on Wed Jan 13 2016
@ashmind (1) ok, assuming that
_inner
belongs to theChanger
class, how would you know that argument passed to the constructor is not shared? (2) I'm thinking in #7626, so "CanReadExternalState" doesn't provide anything useful for immutability enforcement, right?PS: I think the answer to the number (1) is in #160. Perhaps, a type qualifier would be better than
move
I guess.@HaloFour commented on Fri Dec 25 2015
Considering that
PureAttribute
already exists and it has been applied to some percentage of the BCL, assuming (and this is a big assumption) that this has been done using a somewhat consistent set of rules, I think that any direct support for pure functions in the C# compiler should adhere to those same rules.If that's not the case I think that the C# compiler should pick a set of rules and run with it. Trying to adopt many flavors of pure from many different languages seems like a recipe for disaster. However, I could see value in offering that level of information within the syntax tree available to analyzers.
@alrz commented on Fri Dec 25 2015
@HaloFour Not from different languages, these are just concepts tied to immutability, if you want a safe environment to code in, I think it's good to take advantage of these concepts, it encourages you to actually think about isolation and immutability in your design and prevents you to kick yourself in the foot.
@HaloFour commented on Fri Dec 25 2015
@alrz What other languages consider "pure" methods was mentioned by @ashmind. I understand that there are different concepts around immutability, but it doesn't make sense to try to take one concept like "pure" functions and to attempt to accomplish all of them when they differ in design. My point is that the CLR already has "pure" functions, as decorated by the existing attribute, and it makes the most sense for C# to adhere to the same conventions already applied rather than trying to forge its own path, or worse, trying to define some crazy matrix of degrees-of-purity.
@alrz commented on Mon Dec 28 2015
@HaloFour There are two paths that can be taken for immutability enforcement in a language. F# does this by making mutability a special case e.g.
mutable
keyword, but for C# this is not applicable because everything is mutable by default. Deep immutability (#7626) on the other hand, as Eric said, "has strict requirements that may be more than we need, or more than is practical to achieve." Two scenarios in which this becomes a problem are initialization (like #229) and iteration, I can imagine that "isolation" would be helpful in these situations, while it doesn't affect "purity" as far as immutability concerns.For example, if you want to use property initializers or iterating over an immutable list, I think it makes sense if you annotate property setters like
const int P { get; isolated set; }
. Also to be able to useforeach
,GetEnumerator
should be annotated as such, becauseMoveNext
is not pure by definition.@Richiban commented on Mon Feb 08 2016
There's another benefit to having purity enforced by the compiler (or, at least, to have the compiler reasonably confident about purity) -- some of the artificial constraints around covariance would be lifted. For example:
If we define a very simple ISet interface
Unfortunately we can't declare our Set interface as
ISet<out T>
because theContains
method usesT
in an input position; something the language disallows to prevent inserting a Banana into a list of Fruit that is actually a list of Apples.But! In a set you should be able to safely check whether it contains a given item even though the collection is covariant. Why? Because the contains function is pure. So the following could be allowed by the compiler:
Being pure means:
That should cover most of the basics. In theory, if you can't any unpure data (e.g. via properties or methods) then your function kind of has to be deterministic as well...
@jpierson commented on Wed Mar 23 2016
I was just getting ready to propose this exact feature. My assumption was that pure members could only call other pure members. This would be an improvement in cases where I've created static methods just to narrow the reach available to the statements within the method. Could having such a pure keyword assist the complier in optimizations as well? Pure methods should obviously be able to be inferred by the complier in order to make the optimizations so I suppose the use of the keyword would be more about making the contract (for lack of better words) more explicit.
I see this being useful for code readability and developer experience in an IDE or code editor. Example would be when hovering over a method call in a body of code it could indicate that it is pure which gives me an immediate assurance that the method isn't modifying any state. Another option would be to more subtly changing the code syntax colorization to make pure calls distinct.
A possible extension of this could be to allow for a sort of local purity scoped by a class where only fields on the local instance or other members also marked as local pure could be invoked. This would allow class implementations that could guarantee that it doesn't reach out to any global singletons or anything like that. The keyword that would make the most sense here to me would be isolated. Both the proposed pure keyword and a hypothetical isolated keyword seem to be sort of inversely related to the normal access modifiers (public, private, protected, ...). I think it would be crucial to make sure that if introduced that they have an obvious separation in the syntax.
In the example above a class could be marked isolated which would enforce that a class could only contain members that are themselves isolated.
Perhaps the same may make sense for the pure keyword in that it could be used at the class level to ensure that all members are pure members.
@be5invis commented on Sat Feb 18 2017
So... should we use a F*-style effect marker that forms a lattice?
We have Total at the lowermost position (purely functional, guaranteed return), then we have Divergence, State and Exception. And the effect marker of “general” C# functions are CSharp...
cc. @nikswamy @catalin-hritcu @simonpj
Beta Was this translation helpful? Give feedback.
All reactions