[swift-evolution] pure functions

Joe Groff jgroff at apple.com
Wed Aug 23 12:05:11 CDT 2017


> On Aug 18, 2017, at 12:10 PM, Chris Lattner via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Splitting this out from the concurrency thread:
> 
>> 
>> On Aug 18, 2017, at 6:12 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>>> On Aug 17, 2017, at 11:53 PM, Chris Lattner <clattner at nondot.org> wrote:
>>> 
>>>> In the manifesto you talk about restrictions on passing functions across an actor message.  You didn’t discuss pure functions, presumably because Swift doesn’t have them yet.  I imagine that if (hopefully when) Swift has compiler support for verifying pure functions these would also be safe to pass across an actor message.  Is that correct?
>>> 
>>> Correct.  The proposal is specifically/intentionally designed to be light on type system additions, but there are many that could make it better in various ways.  The logic for this approach is that I expect *a lot* of people will be writing mostly straight-forward concurrent code, and that goal is harmed by presenting significant type system hurdles for them to jump over, because that implies a higher learning curve.
>>> 
>>> This is why the proposal doesn’t focus on a provably memory safe system: If someone slaps “ValueSemantical” on a type that doesn’t obey, they will break the invariants of the system.  There are lots of ways to solve that problem (e.g. the capabilities system in Pony) but it introduces a steep learning curve.
>>> 
>>> I haven’t thought a lot about practically getting pure functions into Swift, because it wasn’t clear what problems it would solve (which couldn’t be solved another way).  You’re right though that this could be an interesting motivator. 
>> 
>> I can provide a concrete example of why this is definitely and important motivator.  
>> 
>> My current project uses pure functions, value semantics and declarative effects at the application level and moves as much of the imperative code as possible (including effect handling) into library level code.  This is working out really well and I plan to continue with this approach.  The library level code needs the ability to schedule user code in the appropriate context.  There will likely be some declarative ability for application level code to influence the context, priority, etc, but it is the library that will be moving the functions to the final context.  They are obviously not closure literals from the perspective of the library.
>> 
>> Pure functions are obviously important to the semantics of this approach.  We can get by without compiler verification, using documentation just as we do for protocol requirements that can't be verified.  That said, it would be pretty disappointing to have to avoid using actors in the implementation simply because we can't move pure functions from one actor to another as necessary.
>> 
>> To be clear, I am talking in the context of "the fullness of time".  It would be perfectly acceptable to ship actors before pure functions.  That said, I do think it's crucial that we eventually have the ability to verify pure functions and move them around at will.
> 
> Right.  Pure functions are also nice when you care about thread safety, and there is a lot of work on this.  C has __attribute__((const)) and ((pure)) for example, c++ has constexpr, and many research languages have built full blown effects systems.
> 
> My principle concern is that things like this quickly become infectious: LOTS of things are pure functions, and requiring them all to be marked as such becomes a lot of boilerplate and conceptual overhead.  This is happening in the C++ community with constexpr for example.
> 
> The secondary concern is that you need to build out the model enough that you don’t prevent abstractions.  A pure function should be able to create an instance of a struct, mutate it (i.e. calling non-pure functions) etc.  This requires a non-trivial design, and as the design complexity creeps, you run the risk of it getting out of control.

Now that inout parameters are guaranteed exclusive, a mutating method on a struct or a function that takes inout parameters is isomorphic to one that consumes the initial value as a pure argument and returns the modified value back. This provides a value-semantics-friendly notion of purity, where a function can still be considered pure if the only thing it mutates is its unescaped local state and its inout parameters and it doesn't read or write any shared mutable state such as mutable globals, instance properties, or escaped variables. That gives you the ability to declare local variables and composably apply "pure" mutating operations to them inside a pure function.

We've already brought Swift somewhat into the effects-system design space with "throws" (and "async", if it gets taken as we've currently proposed it), and we already have some abstraction debt to pay off with "throws"; if we wanted to, we could conceivably fold "impure" into that system as well. While it's true that there would be a lot of effort in propagating pure annotations to the right places, that's going to be true of any attempt to move the current everything-goes world into a more robust and constrained framework. I don't think we should write off the notion completely.

-Joe


More information about the swift-evolution mailing list