Swift 3 as of Xcode 8.0b4
TL;DR: I have a struct value type backed by a copy-on-write mutable buffer. You
use it to perform arithmetic on the buffers. The most expressive way to do this
efficiently is to chain the arithmetic operators so each mutates the same
buffer. Swift does not like to chain mutating operators — it treats the result
of each step as immutable, so you can’t continue the chain. I can’t argue; the
syntax apparently can't express anything else.
All the alternatives I see are ugly-to-dangerous.
Have I missed something, I hope? Please make a fool of me.
— F
The details of my use case or implementation are off-topic; even if mine are
ill-considered, surely apt ones exist. Unless you can demonstrate there are
none.
The vDSP_* functions in Apple’s Accelerate framework are declared in C to
operate on naked float or double pointers. I decided to represent such Float
buffers in Swift by a struct (call it ManagedFloatBuffer) containing a
reference to a FloatBuffer, which is a final specialization of class
ManagedBuffer<Int, Float>.
(The names are a work-in-progress. Just remember: ManagedFloatBuffer is a value
type that can copy-on-write to a reference to FloatBuffer, a backing store for
a bunch of Floats.)
The nonmutating funcs:
func subtract(_ other: ManagedFloatBuffer) -> ManagedFloatBuffer
func subtract(_ scalar: Float) -> ManagedFloatBuffer
are straightforward. They return new ManagedFloatBuffer values. You can chain
further calls to simplify a complex calculation that is neither intricate nor
tied up in temporaries:
let sum²OfResiduals = speeds
.subtract(cameraSpeed.mean)
.multiply(feetToMeters)
.sumOfSquares
Great. And vDSP gets you about a 40% boost. (The compiler itself seems to do a
pretty good job of auto-vectorizing; the unoptimized code is a couple of orders
of magnitude slower.) But as you chain the immutables, you generate new
FloatBuffers to hold the intermediate results. For long chains, you end up
allocating new buffers (which turns out to be expensive on the time scale of
vectorized math) and copying large buffers into them that you are about to
discard. I want my Swift code to be as performant as C, but safer and more
expressive.
So how about some mutating functions to change a ManagedFloatBuffer’s bytes
in-place (copying-on-write as needed so you can preserve intermediate values)?
mutating func reduce(by other: ManagedFloatBuffer) -> ManagedFloatBuffer
mutating func reduce(by scalar: Float) -> ManagedFloatBuffer
These return self, because I’d hoped I could chain operators as I did with the
non-mutating versions.
The compiler doesn’t like this. It says reduce(by:) returns an immutable value,
so you can’t chain mutating functions.
(I can see an issue in that when the first func's self is copied as the return
value that is used as the second func’s self, that could make two surviving
references to the same buffer, so a buffer copy would happen when you mutate
the second func’s self anyway. I’m not sure the compiler has to do that, but I
can see how it might be hard to account for otherwise. Hey, it’s a tail call,
right? SMOP, not source-breaking at all.)
StackOverflow invites me to eat cake: Make the mutable operand inout to funcs I
call one by one. Something like:
multiply(perspectiveCorrections, into: &pixelXes)
sin(of: &pixelXes)
multiply(pixelXes, into: &speeds)
multiply(feetToMeters, into: &speeds)
subtract(cameraSpeed.mean, from: &speeds)
let sumSquaredOfResiduals = speeds.sumOfSquares
// grodiness deliberately enhanced for illustration
I’d rather not. The thing to be calculated is named at the bottom of the
paragraph. The intermediate steps must preserve names that change meaning
line-by-line. You have to study the code to recognize it as a single arithmetic
expression.
And by the by, if a vector operand is itself the result of a mutating
operation, the dependency graph becomes a nightmare to read — I can’t be sure
the illustration even expresses a plausible calculation.
Thinking up more reasons to hate this solution is a fun parlor game you and
your family can play at home.
Strictly speaking, the compiler is right: I don’t see any language construct
that expresses that a returned value type that may be mutated by a chained
func. Am I correct?
I’m not at all happy with turning ManagedFloatBuffer into a class. Intuitively,
this is a value type. Passing a packet of Floats into a func (or into another
thread, as one does with math) and finding your Floats had changed in the mean
time is… surprising.
I’m not optimistic, but I have to ask: Is there a way to do this — to take
mutability down an operator chain?
— F
_______________________________________________
swift-users mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-users