> On Feb 19, 2017, at 1:47 PM, Michel Fortin via swift-evolution 
> <swift-evolution@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
> https://michelf.ca
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to