-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
C# Pure Function Keyword to Mark No Side Effects Or External Dependencies #7561
Comments
❓ Considering we already have 📝 Note that I'm not necessarily against this proposal. I'm just trying to get some more context. 😄 |
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. private int x;
public void Unpure() {
x++;
}
[Pure]
public void Pure1() {
Unpure(); // legal, no change to existing code
}
public pure void Pure2() {
x++; // compiler error, side effects
Unpure(); // compiler error, method not marked as pure
Pure1(); // legal, method marked as pure (even if it might not be)
} An analyzer that could issue warnings based on |
Doesn't |
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 |
@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. |
@alrz One option would be to add [Pure(Memoize=true)]
modifiers return_type name(args)
{
body;
} [Pure(SkipVerification=true)]
modifiers return_type name(args)
{
return_type mangled_name(args) {
body;
}
static let memos = new ConcurrentDictionary<ValueTuple<args_types>, return_type>();
static let locks = new ConcurrentDictionary<ValueTuple<args_types>, object>();
return_type result;
let a = ValueTuple.Create(args);
if (!memos.TryGetValue(a, out result)) {
var l = locks.GetOrAdd(a, new object());
lock (l) {
result = memos.GetOrAdd(a, mangled_name);
}
locks.TryRemove(a, out l);
}
return result;
} |
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. |
@leppie Common cases where you may want code that doesn't affect program state but don't need to return a value:
Removing the following would probably not be desirable, yet it's arguably a pure method: public class Requires
{
[Pure]
public static void NotNull<T>(T value, string paramName)
where T : class
{
if (value == null)
throw new ArgumentNullException(paramName);
}
} |
@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. |
@orthoxerox Memoization wouldn't make it to the language. (already proposed in #5205). |
The existing |
This could be implemented as an analyzer. However, it's a bit complicated.
|
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 |
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 |
@ashmind How about |
Local mutation within a method whose variables are not captured (free) would not be impure to me. |
@alrz
That also raises a question of ownership -- let's say we have a class |
@ashmind I didn't understand, having said |
Example:
Is this class changing external state or only state it owns? It's uncertain and depends on whether
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. |
@ashmind (1) ok, assuming that PS: I think the answer to the number (1) is in dotnet/csharplang#6611. Perhaps, a type qualifier would be better than |
Considering that 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. |
@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. |
@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. |
@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. |
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 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... |
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. |
So... should we use a F*-style effect marker that forms a lattice? |
Issue moved to dotnet/csharplang dotnet/roslyn#776 via ZenHub |
Moved the issue over to csharplang repo to continue the discussion there. Thanks |
Actually, Unreal Engine 4 (their visual scripting, not C++) uses pure functions to denote functions that "promise" not to have side effects. I said promise in quotes, because you still can modify things, but the nature of them and the visual scripting meant that they had to basically be used as what would be the equalivent of calling it only as an arguement to another function, ie, using it only for the return value. If it wasn't used for that, it had no way of being called. So, pure is very familiar to me and I think it makes sense. |
@AustinBryan, coincidentaly perhaps, it looks like in Rust-Lang that they have opted for the https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html#const-fn |
Surely benchmarking and unit tests have side effects: they produce reports. |
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.
The text was updated successfully, but these errors were encountered: