Mapping over a tuple

I currently have code that looks like this:


let pxLeft = abs(left.spec.pixelsForDimension(left.dimension, imageSize: size))

let pxRight = abs(right.spec.pixelsForDimension(right.dimension, imageSize: size))

let pxTop = abs(top.spec.pixelsForDimension(top.dimension, imageSize: size))

let pxBottom = abs(bottom.spec.pixelsForDimension(bottom.dimension, imageSize: size))


It's crying our for factorisation, and I'd really love to be able to do this:


let (pxLeft, pxRight, pxTop, pxBottom) = (left, right, top, bottom).map { $0.left.spec.pixelsForDimension($0.dimension, imageSize: size) }


But I can't, since I can't map over a tuple.


Is there a Swifty solution for this sort of thing?

Answered by Jens in 12918022

Regarding the title of the thread, you can't add methods to some/all tuple type(s) since non-nominal types can't be extended. But you can of course write your own (global) map functions for (homogeneous) tuples, here is one for four-tuples:

func map<T, U>(v: (T, T, T, T), transform: T -> U) -> (U, U, U, U) {
    return (
        transform(v.0),
        transform(v.1),
        transform(v.2),
        transform(v.3)
    )
}


And then you can use it like so:

let someTuple = (1.1, 2.2, 3.3, 4.4)
let a = map(someTuple) { UInt8($0 * 10.0) }
let b = map(a) { $0 / 2 }
map(b) { print($0) }


You would have to write overloaded ones for other lengths/counts than four though ...

But anyway, you can write functions for zipping homogenues tuples, extracting elements by index, etc. But what you end up wanting then, would probably be something like a fixed length array type (conforming to CollectionType so you'd get map et al), and then you should look at something along the lines of the example that Dave Abrahams gave as a solution to my problems in this thread.

At only 4 lines, it will be hard to find something simpler than just writing it out. In order to map or iterate, you will have to put your left, right, top, and bottom in a collection of some sort. By the time you do that and then extract the px values, you've done more work than the original. The only time saver I can think of is using a closure. Since I don't know what type top and bottom or pxLeft and pxRight I'll call them Direction and Pixel.


let getPixels: Direction -> Pixel = { dir in abs(dir.spec.pixelsForDimension(dir.dimension, imageSize: size)) }

let pxLeft = getPixels(left)
let pxRight = getPixels(right)
let pxTop = getPixels(top)
let pxBottom = getPixels(bottom)

Hi,


That's not bad actually, it allows me to do this:


let px: DimensionSpec -> CGFloat = { ds in abs(ds.spec.pixelsForDimension(ds.dimension, imageSize: size)) }
let (pxLeft, pxRight, pxTop, pxBottom) = (px(left), px(right), px(top), px(bottom))

Excellent! That looks pretty good.

Accepted Answer

Regarding the title of the thread, you can't add methods to some/all tuple type(s) since non-nominal types can't be extended. But you can of course write your own (global) map functions for (homogeneous) tuples, here is one for four-tuples:

func map<T, U>(v: (T, T, T, T), transform: T -> U) -> (U, U, U, U) {
    return (
        transform(v.0),
        transform(v.1),
        transform(v.2),
        transform(v.3)
    )
}


And then you can use it like so:

let someTuple = (1.1, 2.2, 3.3, 4.4)
let a = map(someTuple) { UInt8($0 * 10.0) }
let b = map(a) { $0 / 2 }
map(b) { print($0) }


You would have to write overloaded ones for other lengths/counts than four though ...

But anyway, you can write functions for zipping homogenues tuples, extracting elements by index, etc. But what you end up wanting then, would probably be something like a fixed length array type (conforming to CollectionType so you'd get map et al), and then you should look at something along the lines of the example that Dave Abrahams gave as a solution to my problems in this thread.

Hi Jens,


Your solution's obvious now that you've posted it 🙂


Thanks,


Tim

Mapping over a tuple
 
 
Q