[swift-evolution] [swift-evolution-announce] [Review] SE-0099: Restructuring Condition Clauses

Brent Royal-Gordon brent at architechies.com
Sat May 28 01:25:15 CDT 2016


> Let me answer in another way that speaks to my background which isn't in compiler theory: The use of && may produce cognitive overload between the use in Boolean assertions and the use in separating condition clauses.

Yes, which is quite intentional on my part. The `if` statement requires that all of its clauses succeed; if pattern matching and optional testing were boolean expressions, you would use `&&` to link them with each other and with boolean tests. The fact that these are *not* boolean expressions is a mere artifact of Swift's implementation.

I think our best solution is to make Swift act as though these *are* boolean expressions, but ones that can only be used in a limited way: they can only be `&&`ed, because they bind variables that have to be made available in specific blocks. In other words, I think we should paper over the compiler limitations preventing these things from working as expected.

(Actually, it might be interesting to allow `!let` and `!case` statements which are available in the `else` branches of the control structures they're used in, but that's a different story...)

***

If you'll permit me to go sort of "mad dream" here for a moment, I can actually sort of see a way to do a lot of this in the standard library. Imagine if the `let` and `case` clauses in a conditional produced a type like this:

	enum PatternMatchingResult<BoundValues> {
		case failed
		case succeeded (BoundValues)
	}

`BoundValues` would be the values, if any, extracted through the pattern matching operation. Then you could define operators like these:

	func && <T, U>(lhs: PatternMatchingResult<T>, rhs: @autoclosure () -> PatternMatchingResult<U>) -> PatternMatchingResult<(T, U)> {
		guard case .succeeded (let lhsValue) = lhs else {
			return .failed
		}
		guard case .succeeded (let rhsValue) = rhs() else {
			return .failed
		}
		return .succeeded (lhsValue, rhsValue)
	}

	func && <T>(lhs: PatternMatchingResult<T>, rhs: @autoclosure () -> Boolean) -> PatternMatchingResult<T> {
		guard case .succeeded = lhs else {
			return .failed
		}
		guard rhs() else {
			return .failed
		}
		return lhs
	}
	
	func && <U>(lhs: Boolean, rhs: @autoclosure () -> PatternMatchingResult<U>) -> PatternMatchingResult<U> {
		guard lhs else {
			return .failed
		}
		return rhs()
	}

And then transform this:

	guard
		x == 0 && a == b && c == d &&
		let y = optional, w = optional2, v = optional 3 &&
		z == 2
	else { ... }

Into something like this (where `?` is a sort of "anonymous capture slot"):

	guard case let .success (y, w, v) = (
		x == 0 && a == b && c == d &&
		Pattern(.some(?), .some(?), .some(?)).result(ofMatchingAgainst: (optional, optional2, optional3)) &&
		z == 2
	)
	else { ... }

Resolving to:

	guard case let PatternMatchingResult.success (y, w, v) = (
		(&&)(	// (Boolean, PatternMatchingResult) -> PatternMatchingResult
			x == 0,
			(&&)( 	// (Boolean, PatternMatchingResult) -> PatternMatchingResult
				a == b,
				(&&)(	// (Boolean, PatternMatchingResult) -> PatternMatchingResult
					c == d,
					(&&)(	// (PatternMatchingResult, Boolean) -> PatternMatchingResult
						Pattern(.some(?), .some(?), .some(?)).result(ofMatchingAgainst: (optional, optional2, optional3)),
						z == 2
					)
				)
			)
		)
	)
	else { ... }

The `Pattern` type shown here is notional, not an actual thing that would exist as a first-class entity—although that *would* be rather nice to have eventually...

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list