Skip to content

Instantly share code, notes, and snippets.

@karwa
Last active January 7, 2017 05:51
Show Gist options
  • Save karwa/52b35a8a1f3bebc24264df5aeb2aa761 to your computer and use it in GitHub Desktop.
Save karwa/52b35a8a1f3bebc24264df5aeb2aa761 to your computer and use it in GitHub Desktop.
Ideas for Swift 4

Specialising based on parameter values

The compiler would statically optimise a function with particular values, and generate an entry point which checks for the values and runs the optimised implementations.

@_specialize(value: 42)
// Some function on our hot path
func someFunction(value: Int) -> Result? {
  if value == 42 {
     // magic value. Do some special processing.
  }
  
  // Lots of common processing...
  // Lots of common processing...
  
  if value != 42 {
     // Ignore the magic value for this stuff.
  }
}

The compiler would generate something like:

// Entry point
func someFunction(value: Int) -> Result? {
  if value == 42 {
      return someFunction_42()
  }
  else {
    return someFunction_?(value: value)
  }
}

func someFunction_42() { /* Optimised implementation of someFunction where value == 42 */ }
func someFunction_?(value: Int) { /* Standard version of someFunction, where value == ? */ }

It should be possible to specify multiple specialised values (e.g. value == 42, value == 57, value == 78, etc), and also to combine with other parameters (although this could increase code-size considerably. It should be used with caution):

@_specialize(value: 42, 57, 78)
@_specialize(withSideEffects) // Bool with no value expanded to include both `true` and `false`
func someFunction(value: Int, withSideEffects: Bool) -> Result? {
  // Some code
}

Generates:

// Entry point
func someFunction(value: Int, withSideEffects: Bool) -> Result? {
    if value == 42 {
        return withSideEffects ? someFunction_42_true() : someFunction_42_false()
    }
    else if value == 57 {
        return withSideEffects ? someFunction_57_true() : someFunction_57_false()
    }
    else if value == 78 {
        return withSideEffects ? someFunction_78_true() : someFunction_78_false()
    }
    else {
        return someFunction_?_?(value: value, withSideEffects: withSideEffects)
    }
}

The attribute is basically a hint to the compiler that we branch on a specific value that the code-path may be radically different for a few salient values. The compiler is able to reduce all of these branches in to one branch at the entry-point, which may allow for more favourable inlining in the specialised functions.

rethrow keyword

When building a rethrowing function (such as map), sometimes you need to make use of functions which are not explicitly rethrowing. It would be nice if we could tell the compiler "this error that I'm throwing only ever happens if the closure throws". The compiler may or may not be able to statically verify that, but if it can't, violating that contract would either cause a fatal error or be undefined behaviour:

func someFunctionImportedFromC(closure: (Int, inout Bool)->())

func myFunction<T>(closure: (Int) throws ->Void) rethrows -> Void {

  var _error: Error?
  someFunctionImportedFromC{ value, stop in
      do    { try closure(value) }
      catch { _error = error; stop = true }
  }
  guard _error == nil else { rethrow error! } // 'rethrow' means this error came from the closure, guaranteed.
}

Enable tuple types to have default values:

public enum ReadError : Error {
    case otherError(description: String, location: StaticString? = #function) // <- currently an error, tuples can't have default values
}

Bridge DispatchData to Foundation Data

(or introduce convenience intialiser on Foundation Data). Currently the incantation is ugly and introduces a needless copy when the DispatchData is non-contiguous:

dispatchData.withUnsafeBytes { (ptr: UnsafePointer<Int8>) -> Void in
    let foundationData = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: ptr), 
                                    count: dispatchData.count,
                              deallocator: .none)
    // use the data as Foundation Data within this closure.
}

Allow function requirements in protocols to be satisfied by closure-type properties

Among other things, it would ease the implementaion of type-erased wrappers.

protocol MyProtocol {
    func run(_ param: Int) -> String
}

struct MyStruct : MyProtocol {
    var run : (Int)->String   // Satisfies requirement MyProtocol.run
}

Allow functions with default arguments to satisfy protocol requirements

This is just annoying. Sometimes I want to add some (optional) implementation-specific parameters to a members of a type which conforms to a protocol.

protocol Sportsplayer {
    func goalsScored() -> Int
}

struct SoccerPlayer: Sportsplayer {
    struct GoalType : RawOptionSet {
        static let Shot   = GoalType(0x1)
        static let Volley = GoalType(0x10)
        static let Header = GoalType(0x100)
        static let Any    = GoalType(0x111)
    }

    // Default value .Any means this conforms to Sportsplayer
    func goalsScored(type: GoalType = .Any) {
      //...
    }
}

struct Footballer: Sportsplayer {
    struct GoalType : RawOptionSet {
        static let Touchdown = GoalType(0x1)
        static let FieldGoal = GoalType(0x10)
        static let Any       = GoalType(0x11)
    }

    // Default value .Any means this conforms to Sportsplayer
    func goalsScored(type: GoalType = .Any) {
      //...
    }
}

(unowned self).myFunc syntax to forward methods without retaining

Consider the following code:

protocol Task {
    associatedType Frame
    var handleFrame: (Frame) throws -> Void { get set }
    func start()
}

class SomeTask: Task {
    var handleFrame: (SomeTask.Frame) throws -> Void
    func start() {
        // ...
    }
}

class ComposedTask: Task {
    private let underlying: SomeTask
    
    init() {
        underlying = SomeTask()
        underlying.handleFrame = { [unowned self] in
            return try self.processFrame($0)
        }
    }
    
    func processFrame(frame: SomeTask.Frame) throws {
        try handleFrame(ComposedTask.Frame(frame))
    }
    
    var handleFrame: (ComposedTask.Frame) throws -> Void
    func start() {
        underlying.start()
    }
}

In this case, the part of the initialiser where we forward underlying.handleFrame to ComposedTask.processFrame is awkward. As a small syntactic enhancement, it would be nicer to be able to write:

underlying.handleFrame = (unowned self).processFrame

Allow specialising members of generic types based on where clauses:

The following code fails to compile due to an ambiguous use of doIt(). It should be allowed, the constrained one is clearly more specialised than the generic one.

protocol DEF {}
extension Array : DEF{}

struct ABC<T> {

  let op : T
  init(with: T) { self.op = with }

  func doIt() {
    print("generic function")
  }
}

extension ABC where T:DEF {
  func doIt() {
    print("Specialised function")
  }
}

ABC(with: [1, 2, 3]).doIt() // ERROR: ambiguous use of 'doIt()'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment