Skip to content

Instantly share code, notes, and snippets.

@erica
Last active February 2, 2016 17:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/6327981d42eb9be6b4d2 to your computer and use it in GitHub Desktop.
Save erica/6327981d42eb9be6b4d2 to your computer and use it in GitHub Desktop.

Automating Partial Application via Wildcards

Introduction

SE-0002 has been accepted for Swift 3.0. That proposal removes currying func declaration syntax from Swift. It reasons that currying introduces unnecessary language and implementation complexity and is easily replaced with chained function return types.

Because of SE-0002, this curried example:

public func projectFunctionToCoordinateSystem(function f: FunctionType)(p0: CGPoint, p1: CGPoint)(x: CGFloat) -> CGPoint

becomes

public func projectFunctionToCoordinateSystem(function f: FunctionType) -> (p0: CGPoint, p1: CGPoint) -> (x: CGFloat) -> CGPoint 

in Swift 3.

It's mechanically simple to re-introduce partial application but the current solution adds unnecessary nesting and complicated closure declarations, as you see in the following Swift 3 version of this projection function.

public func projectFunctionToCoordinateSystem(function f: FunctionType) -> (p0: CGPoint, p1: CGPoint) -> (x: CGFloat) -> CGPoint {
  return { p0, p1 in
    return { x in
      let (dx, dy) = (p1.x - p0.x, p1.y - p0.y)
      let (magnitude, theta) = (hypot(dy, dx), atan2(dy, dx)) // Thanks loooop
      var outPoint = CGPoint(x: x * magnitude, y: f(x) * magnitude)
      outPoint = CGPointApplyAffineTransform(outPoint, CGAffineTransformMakeRotation(theta))
      outPoint = CGPointApplyAffineTransform(outPoint, CGAffineTransformMakeTranslation(p0.x, p0.y))
      return CGPoint(x: outPoint.x, y: outPoint.y)
    }
  }
}

SE-0002 mentions the possibility of introducing Scala-style free-form partial implementation as a future step. This proposal requests that a Scala-style wildcard feature be adopted into Swift by introducing a form of automatic partial application.

Detail Design

The proposed design replaces a Swift 3 curried signature like this:

public func projectFunctionToCoordinateSystem(function f: FunctionType) -> (p0: CGPoint, p1: CGPoint) -> (x: CGFloat) -> CGPoint

with a non-curried, fully qualified call like this:

public func projectFunctionToCoordinateSystem(function f: FunctionType, p0: CGPoint, p1: CGPoint, x: CGFloat) -> CGPoint

When called with wildcard tokens, the function is partially applied using the supplied arguments. For example:

let partial1 = projectFunctionToCoordinateSystem(function: mySinFunction, p0: p0, p1: p1, x: _)
// partial1(x: xValue)

let partial2 = projectFunctionToCoordinateSystem(function: mySinFunction, p0: .zero, p1: _, x: _)
// partial2(p1: p1Value, x: xValue) 
// or
// let partial3 = partial2(p1: p1Value, x: _); partial3(x: 0.25)

This returns a curried form. Labels are retained and used, and the compiler should throw an error for any ambiguity that arises as a side effect.

The function implementation is fully configured as if all parameters are specified, losing all its nested params in/params in/params in... overhead:

public func projectFunctionToCoordinateSystem(function f: FunctionType, p0: CGPoint, p1: CGPoint, x: CGFloat) -> CGPoint {
  let (dx, dy) = (p1.x - p0.x, p1.y - p0.y)
  let (magnitude, theta) = (hypot(dy, dx), atan2(dy, dx)) // Thanks loooop
  var outPoint = CGPoint(x: x * magnitude, y: f(x) * magnitude)
  outPoint = CGPointApplyAffineTransform(outPoint, CGAffineTransformMakeRotation(theta))
  outPoint = CGPointApplyAffineTransform(outPoint, CGAffineTransformMakeTranslation(p0.x, p0.y))
  return CGPoint(x: outPoint.x, y: outPoint.y)
}

The result is simple, readable, and written independently of how the currying is to be applied. as under this design, any parameter can be curried.

Alternatives Considered

Although our natural inclination is to use standard currying and partial application, we also considered defaulting arguments. In this scenario, wildcards return a version of the function with defaulted arguments for all non-wildcard values. In such a design,

let defaultedVersion = projectFunctionToCoordinateSystem(function: mySinFunction, p0: .zero, p1: _, x: _)

could be called with a p0 parameter even though that same parameter was already specified in the assignment as in the following example. So this:

defaultedVersion(p0: myNewOrigin, p1: myPoint, x: 0.5)

expands to:

defaultedVersionOfProjectFunctionToCoordinateSystem(function: mySinFunction, p0: myNewOrigin, p1: myPoint, x: 0.5)

where the new version of p0 overrides the defaulted version created in the initial wildcard assignment.

The implementation details for this alternative approach would differ but it might be easier to implement. As you'd expect, any function called with fully qualified arguments would be executed rather than returning a defaulted version (or a partially applied version for the non-alternative implementation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment