[swift-evolution] [Pitch] Support for pure functions. Part n + 1.
matthew at anandabits.com
Sun Feb 19 14:19:05 CST 2017
> On Feb 19, 2017, at 1:47 PM, Michel Fortin via swift-evolution <swift-evolution at swift.org> wrote:
> I'm a bit disappointed to see this discussion bikeshedding the syntax without clarifying much the semantics. Here's a few question of semantic nature.
+1. I think the syntactic questions are important mostly because we need an answer that works for something as lightweight as a single expression closure without sacrificing the syntactic concision. But the semantic questions are far more important and complex.
> The base principle of a pure function is that it has no side effects. But it's quite clear that at least some side effects will need to be allowed. So which ones?
> 1. Many basic calculations will leave flags in registers that persist until the next computation. Those can be checked by the program later (after the function is run in some cases). That's probably a side effect you want to ignore.
> 2. Floating point operations can give different results depending on flags you can set in the registers. Things like setting the rounding mode for instance. Will the pure function need to reset those flags before performing floating point operations so it can guaranty the same result every time? What are the implication if those are ignored?
> 3. Is a pure function allowed to dereference pointers or object references passed as parameters? A pointer or an object reference might provide access to the global state of the program.
The answer I’ve seen thus far is yes, but only if the state that is referenced is constant after initialization or the access is marked with some kind of “trust me” annotation.
> 4. Allocating memory requires access to the global memory layout of the program. And allocating will give you a different memory address every time. Are you allowed to allocate memory in a pure function?
I think we have to allow this (i.e. to lazily initialize a memorization cache) but it should probably require the “trust me” annotation.
> 5. Many basic operations in the language will implicitly allocate memory or dereference pointers. Same for the containers in the standard library. If you don't allow memory allocations or pointer dereferencing, what subset of the language is still usable in a pure function?
This feels the same as #4. There will be parts of the standard library that use the “trust me” annotation and are therefore considered pure at the point of use despite doing these things.
> 6. If you do allow memory allocations inside the function, is it safe to instantiate and return a new class?
This is an interesting question. If the instance is immutable my instinct is yes, at least in many cases (immutable classes can have value semantics). If not, probably not?
> 7. Is it desirable that the optimizer sometime take the pure attribute to heart to combine multiple apparently redundant calls into a single one? Or is pure not intended to be usable for compiler optimizations? The ability to optimize will likely be affected by the answer to these question and the loopholes you are willing to allow.
I think the answer has to be yes. If it isn’t, what kind of purity do we really have?
This seems like a very good criteria to keep in mind in answering all of these questions.
> 8. Is exiting the program (`fatalError`, etc.) allowed? That's a pretty big side effect. Although it could also be considered as no side effect since the program cannot go further.
Good question. Does a pure function have to be total? Allowing `fatalError` in a pure function is kind of like allowing it to be partial. Some inputs crash rather than produce a result. I would like to say no, but I think the engineering tradeoffs already made in Swift require us to say yes. If not, you couldn’t do anything that might cause the language or library to trap (array subscript for example).
> 9. Is throwing allowed? Maybe the thrown error should considered simply as different return value.
Yes, throwing should be allowed as long as the same error is always thrown for the same input. Consider Joe Groff’s recent suggestion for typed error handling. Under that proposal *every* function can be thought of as returning a `Result`. The error type for non-throwing functions is `Never` and for untyped throwing functions it is `Error`. Throwing is implemented differently than a `Result` return type is conceptually similar.
> 10. Is += allowed inside a pure function? Operators are functions too, but can += be made pure with `inout` and no return value?
Local mutation should be allowed. Mutation that can affect external state should not. This is one of the really great aspects of the value semantics approach to values as opposed to the immutable / pure functional approach to values.
> 11. Can you use a BigInt implementation in a pure function? BigInt needs to allocate internally.
Yes. It is pure in terms of the conceptual values it returns for a given input. This is one of the most important properties of purity. The other is that pure functions safe to use concurrently (if they do access shared mutable state such as a memorization cache the synchronization should be handled as an implementation detail).
> 13. Say you want to keep logs of what happens in the function in order to debug something: is there an "unsafe" way to do IO for that purpose? Or maybe you want to implement `fatalError` as a pure function: is there a way to print something before exiting?
An escape hatch for debugging is reasonable, but we need to be careful that it is not possible to abuse it for other purposes. For example, it should not be possible to append to a (non-debug) log without threading some kind of context through (i.e. something like a state or writer monad).
> Basically, there is no such thing as "no side effect". You need to pick which side effects are acceptable and then evaluate where your picks puts you on the scale between too lax (and of little utility) and too restrictive (and not usable outside of trivial examples). It might be a good idea to make the answer to all those questions clear in the proposal so people have an idea of what they can expect pure functions to do and what guaranties they provide.
I agree that we need to define the semantics carefully and as thoroughly as possible. If this were easy I would have written a proposal already! :)
> Michel Fortin
> swift-evolution mailing list
> swift-evolution at swift.org
More information about the swift-evolution