[swift-evolution] [Review] SE-0161: Smart KeyPaths: Better Key-Value Coding for Swift

Brent Royal-Gordon brent at architechies.com
Sat Apr 1 21:24:29 CDT 2017


> On Apr 1, 2017, at 5:56 PM, Karl Wagner via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> let isPuppyQualifier = Pet.type.equals(.dog).and(Pet.age.lessThan(12))
>> let familyQualifier = Family.pets.hasAtLeastOne(satisfying: isPuppyQualifier)
>> let familiesWithPuppies = Family.fetch(editingContext, familyQualifier)
>> 
>> For those unfamiliar with EOF, the editingContext in the code above is an EOEditingContext which is analogous to NSManagedObjectContext. 
> 
> Theoretically, you could do something like that with this proposal...
> 
> struct UnaryPredicate<Parameter, Result> {
>     let evaluate: (Parameter) -> Result
> }
> struct BinaryPredicate<Left, Right, Result> {
>     let evaluate: (Left, Right) -> Result
> }
> 
> extension KeyPath where Value: Equatable {
>     func equals(_ value: Value) -> UnaryPredicate<Root, Bool> {
>         return UnaryPredicate { $0[keyPath: self] == value }
>     }
>     func equals<KP: KeyPath>(_ other: KP) -> BinaryPredicate<Root, KP.Root, Bool> where KP.Value == Value {
>         return BinaryPredicate { $0[keyPath: self] == $1[keyPath: other] }
>     }
> }
> 
> let isDog = #keypath(Pet, .type).equals(.dog) // UnaryPredicate<Pet, Bool>
> if isDog.evaluate(somePet) {
>     print(“It’s a dog”)
> }
> 
> let areSameLength = #keypath(Array<Int>, .count).equals(#keypath(Array<String>, .count))
> // BinaryPredicate<Array<Int>, Array<String>, Bool>
> if areSameLength.evaluate([1,2,3], [“a”, “b”, “c”]) {
>     print(“same lengths”)
> }


You guys aren't thinking big enough.

	// This implementation is closure-based because it's too complex for an email even *with* 
	// generics system upgrades. Without type system upgrades, things get *really* complicated, 
	// though probably still tractable.
	typealias Predicate<Element> = (Element) -> Bool
	
	func == <Root, Value: Equatable> (lhs: KeyPath<Root, Value>, rhs: Value) -> Predicate<Root> {
		return { $0[keyPath: lhs] == rhs }
	}
	
	func < <Root, Value: Comparable> (lhs: KeyPath<Root, Value>, rhs: Value) -> Predicate<Root> {
		return { $0[keyPath: lhs] < rhs }
	}

	func && <Root>(lhs: Predicate<Root>, rhs: Predicate<Root>) -> Predicate<Root> {
		return { lhs($0) && rhs($0) }
	}

	extension KeyPath where Value: Collection {
		func contains(where predicate: Predicate<Value.Element>) -> Predicate<Value> {
			return { $0.contains(where: predicate) }
		}
	}

That gives you:

	let isPuppyQualifier = #keyPath(Pet, .type) == .dog && #keyPath(Pet, .age) < 12
	let familyQualifier = #keyPath(Family, .pets).contains(where: isPuppyQualifier)
	let familiesWithPuppies = Family.fetch(editingContext, familyQualifier)

Or, in one line with a more sugary syntax:

	let familiesWithPuppies = Family.fetch(editingContext, (.pets).contains(where: .type == .dog && .age < 12))

-- 
Brent Royal-Gordon
Architechies

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170401/ceaceb5f/attachment.html>


More information about the swift-evolution mailing list