Replies: 58 comments 3 replies
-
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.
-
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.
-
A lot of related discussions are also here: dotnet/roslyn#15079 |
Beta Was this translation helpful? Give feedback.
-
@tannergooding @HaloFour - Sure, but what is lost by saying for a method to be pure it must be static in a static class/struct? Extension methods could probably have been implemented in other clever ways but they are static methods in static classes period. This is the way it's done and nobody has any problems with this. Also @HaloFour look at my earlier reply to a similar question. That scenario cannot arise with static/static. |
Beta Was this translation helpful? Give feedback.
-
It wouldn't be illegal to access state, it is only illegal to access mutable state from a pure method (as you have no guarantee that the input is deterministic). |
Beta Was this translation helpful? Give feedback.
-
The fact that you have to duplicate API design and use "hacks" (expose a static member that takes the You then have user-defined "primitives" (think |
Beta Was this translation helpful? Give feedback.
-
My point is that I may want to write If you allow that then we comes that instance methods are just static methods with extra |
Beta Was this translation helpful? Give feedback.
-
@tannergooding - well I did write "instance state" and this all boils down to the reality that there is a By forcing the arguments to a pure method to never be implicit (this, instance methods) the pattern for them becomes simple and free of possible misunderstandings. Any developer waning a pure method simply adheres to that pattern static/static. Extension methods do this (and perhaps a similar thing could be considered for purity) the |
Beta Was this translation helpful? Give feedback.
-
You're precluding a large quantity of potentially pure instance members for no reason which drastically reduces the usefulness of the feature. There's no reason to do this. Non-virtual instance members can be just as pure as static members that accept a reference.
Yes they do, which is why DIM is being implemented, as well as "shapes"/extension-implementation/whatever-its-called-today. Extension methods aren't static because they need to be. They're static because it was easier to implement. |
Beta Was this translation helpful? Give feedback.
-
But string PureConcat(string a, string b) => a + b can be static, if a method never accesses instance state it is to all intents and purposes static so why not insist it be coded as static? |
Beta Was this translation helpful? Give feedback.
-
Why insist that it has to be? What if it does access state? What about this? public class FooString {
public readonly string a;
public FooString(string a) => this.a = a;
public string PureConcat(string b) => a + b;
} That instance method is perfectly pure, regardless of the fact that it accesses state. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour you're too quick, I wish I could comment this earlier 😄 |
Beta Was this translation helpful? Give feedback.
-
Because it makes no sense to purposefully hinder the language. CoreFX has a lot of code and APIs (I'm sure we have numbers somewhere, but I don't remember where). Forcing |
Beta Was this translation helpful? Give feedback.
-
The subject of purity is fundamental and supporting that would allow the compiler to do things that it cannot do today, the goal is to be able to do those things. I am not against supporting this at instance level if that is technically possible but conceptually I see no advantage personally. If I could only implement pure methods as static members in static classes I'd have no issue at all with that. So I'm not saying it cannot be done at instance level only that conceptually I see no benefit. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour - yes it is pure and if this is desired then I'm fine, I just do not see any advantage in potentially obfuscating purity. Someone could edit that class and break purity yet not realize. This is where the need for attributes or keywords come in, to alert the developer to this situation. By insisting purity must be static/static in implementation no new attributes or keywords are needed and surely this is a desirable thing when improving the language? I was under the impression that simplicity of implementation was desirable when enhancing C#. |
Beta Was this translation helpful? Give feedback.
-
I'm not really arguing here, and I may have missed something subtle, but this is how I'm perceiving this. |
Beta Was this translation helpful? Give feedback.
-
@Korporal Oh, you can totally just detect purity / impurity (although it might not sometimes be possible?) but I'm saying you wouldn't want to. Imagine if the compiler just 'detected' if what you were currently writing was static or instance? It would be far too easy to accidentally introduce errors in your program with a seemingly innocuous change, errors would cascade throughout the program potentially making them very hard to to track down ("why isn't this function pure? It's supposed to be pure. What function calls what function that is ultimately impure?"). |
Beta Was this translation helpful? Give feedback.
-
Okay, what's about class Config
{
public static int GetMaxConnectionCount(Config config) => 5
}
const int Foo = Config.GetMaxConnectionCount(new Config()) |
Beta Was this translation helpful? Give feedback.
-
Actually my example is looking interesting, it seems from an experiment here that code built against v1.0 runs fine when run against v1.1 - this is unexpected to me. Is this a bug?? OK I see what's happening, at runtime the app doesn't even load the library assembly because the compiler extracted that const value and embedded that in the IL. Therefore when run against a later build of the library (that no longer has const but static) it works because as I say, it has no need to load the assembly... |
Beta Was this translation helpful? Give feedback.
-
Imagine you have a struct: public struct MyStruct
{
public int x;
} There is then no difference (from a purity perspective) between: public static int GetInt(MyStruct s) => s.x; and the following (where public int GetInt() => return x; For any given deterministic input of |
Beta Was this translation helpful? Give feedback.
-
It doesn't have an attribute, but it is utilizing a well-known language keyword that enforces the desired semantics ( |
Beta Was this translation helpful? Give feedback.
-
@Pzixel No I don't think it is because the library provider could make this change and the consumer would behave differently: class Other
{
public static int GetMaxConnectionCount(Config config) => 4;
}
class Config : Other
{
//public static int GetMaxConnectionCount(Config config) => 5;
} These scenarios cannot arise when static classes and static methods. |
Beta Was this translation helpful? Give feedback.
-
That is because the v1.0 code was constant and so code compiled against it never loads the now static field (constants aren't used at runtime, they are only used at compile-time). |
Beta Was this translation helpful? Give feedback.
-
The library provider making a breaking change can't be prevented in any scenario. The same thing exists with If you make a breaking change, you may break consumers of your library or give them subtle bugs/behavioral differences. |
Beta Was this translation helpful? Give feedback.
-
Changing the backing implementation would be a behavioral change and could lead to subtle bugs (existing code would still work, but you could for example observe a difference between code the compiler emits and code that is dynamically invoked at runtime). |
Beta Was this translation helpful? Give feedback.
-
But it's just a function parameter. You don't allow reference types to be valid arguments for a const expression? Then it will be unusable. You literally cannot format two literals this way. |
Beta Was this translation helpful? Give feedback.
-
@tannergooding - all true but instance members can be overridden or hidden. I do not see any problem with making the rule that pure methods must be static inside static classes/structs. Such a rule eliminates override possibilities. This would force the developer of course to use this: public static int GetInt(MyStruct s) => s.x; But since the desired behavior is what's important such a restriction is minor, cosmetic surely. |
Beta Was this translation helpful? Give feedback.
-
@Pzixel - I don't quite understand your point. We want pure methods to return results that vary only when input arguments vary, the same argument values yield the same return result always. This is perfectly accomplished by What pure method can one write that is instance based that one cannot write based on statics? This "restriction" then makes it easier surely for the language and compiler to support because at a stroke you eliminate any possibility of accessing instance state because it's already illegal in C#. |
Beta Was this translation helpful? Give feedback.
-
Not allowing a A method being hidden also does not prevent the hidden method from being pure or from someone accessing that hidden member. It just means that Don't forget that you can also hide static members 😄 |
Beta Was this translation helpful? Give feedback.
-
Instance members can only be overridden if they are |
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