First draft. Let's go Scala!

-- E

Automating Partial Application via Wildcards

Proposal: TBD

Author(s): Erica Sadun <http://github.com/erica>, Davide De Franceschi <http://github.com/DeFrenZ>

Status: TBD

Review manager: TBD

<davide.md · GitHub

SE-0002 <https://github.com/apple/swift-evolution/blob/master/proposals/0002-remove-currying.md> 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.

<davide.md · GitHub 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.

<davide.md · GitHub 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).