swiftfunctional-programmingmutating-function

Swift mutating Function as first class value


I can have a function to swap the first two elements of an array:

func swapFirstTwo(array: inout [Int]) {
  if array.count >= 2 {
    array.swapAt(0, 1)
  }
}

typealias Swapper = (inout [Int]) -> ()

// And I can have a variable = the function
let swapThem: Swapper = swapFirstTwo

// And it works like this:
var array = [1,2,3]
print(array)
swapThem(&array)
print(array)

// But I'm allergic to Global functions!
// It would be more swifty to have:

extension Array where Element == Int {
  mutating func swapFirstTwo2() {
    if count >= 2 {
      swapAt(0, 1)
    }
  }
}

typealias Swapper2 = (inout [Int]) -> () -> ()

// But when I do this:
let swapThemAgain: Swapper2 = Array.swapFirstTwo2
// I get the error:
// Partial application of 'mutating' method is not allowed; calling the function has undefined behavior and will be an error in future Swift versions

var array2 = [1,2,3]
print(array2)
array2.swapFirstTwo2()
print(array2)
// This in fact works but I've tried similar things and sometimes they appear to be unstable.
// How can I achieve doing: array2.swapFirstTwo2() without getting the error?

This in fact works but I've tried similar things and sometimes they appear to be unstable. Also the compiler warning needs to be heeded. How can I achieve doing: array2.swapFirstTwo2() without getting the warning/error?


Solution

  • The reason why you get a warning (and soon to be an error in Swift 5 mode) on:

    extension Array where Element == Int { 
      mutating func swapFirstTwo2() {
        if count >= 2 {
          swapAt(0, 1)
        }
      }
    }
    
    typealias Swapper2 = (inout [Int]) -> () -> ()
    let swapThemAgain: Swapper2 = Array.swapFirstTwo2
    

    is due to the fact that inout arguments are only valid for the duration of the call they're passed to, and therefore cannot be partially applied.

    So if you were to call the returned (inout [Int]) -> () -> () with &array2, you would get back a () -> () which now has an invalid reference to array2. Attempting to call that function will yield undefined behaviour, as you're trying to mutate an inout argument outside of the window where it's valid.

    This problem will be fixed if/when unapplied instance methods get flat signatures, as Array.swapFirstTwo2 will instead evaluate to a (inout [Int]) -> ().

    But in the mean time, you can workaround the issue by using a closure instead:

    typealias Swapper2 = (inout [Int]) -> ()
    let swapThemAgain: Swapper2 = { $0.swapFirstTwo2() }
    
    var array2 = [1,2,3]
    print(array2) // [1, 2, 3]
    array2.swapFirstTwo2()
    print(array2) // [2, 1, 3]