Skip to content

Instantly share code, notes, and snippets.

@anandabits
Created February 12, 2018 15:46
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 anandabits/fad4f47d35ab76067c49649ce064b089 to your computer and use it in GitHub Desktop.
Save anandabits/fad4f47d35ab76067c49649ce064b089 to your computer and use it in GitHub Desktop.
A (non-working) experiment in abstracting over constraints in Swift
// NOTE: This is an experiement that does not actually work (in the 2/8/18 nightly toolchain).
// The idea is to use Swift's constraint inference to be able to abstract an arbitrary set of constraints.
/// An empty enum serving as an example of the general case of
/// representing a set of constraints that relate multiple types.
/// This specific example provides the constraint that both types are sequences and they have the same Element type.
enum Parallel<S1: Sequence, S2: Sequence> where S1.Element == S2.Element {
typealias First = S1
typealias Second = S2
}
/// A function that uses a phantom argument intended to get the compiler to infer the constraints
/// represented by the Parallel.
/// This seems to work but can produce extremely unhelpful error messages, far worse than the error messages
/// that would be produced if the constraints were stated explicitly here. It is also rather verbose at the usage site.
func foo<S1, S2>(_ s1: S1, _ s2: S2, constriants: Parallel<S1, S2>.Type = Parallel<S1, S2>.self) {
print(s1)
print(s2)
}
// compiler allows this as expected
foo([1, 2], [1, 2] as Set)
// Compiler rejects this with a useful error message:
// - candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'S1.Element' == 'S2.Element' [with S1 = [Int], S2 = Set<String>])
// foo([1, 2], ["1", "2"] as Set)
// Compiler rejects this but with a non-obvious error message:
// - cannot invoke 'foo' with an argument list of type '(Int, Int)'
// - note: expected an argument list of type '(S1, S2, constriants: Parallel<S1, S2>.Type)'
// foo(42, 43)
// Given the prior two examples it appears that the compiler infers the constraints on S1 and S2
// as desired when the types both have Sequence conformances despite not meeting other constraints
// specified by Parallel, but only omits the default argument when they do not have the conformances.
/// A function that uses a where clause intended to get the compiler to infer the constraints
/// represented by the Parallel. Ideally this could simply be stated as Parallel<S1, S2>.
func bar<S1, S2>(_ s1: S1, _ s2: S2) where S1 == Parallel<S1, S2>.First {
print(s1)
print(s2)
}
foo([1, 2], [1, 2] as Set)
// Compiler unfortunately allows this.
bar([1, 2], ["1", "2"] as Set)
// Compiler does not allow this but fails with the same unhelpful diagnostic as in foo(42, 43)
//foo(42, 43)
/// A type that uses a where clause intended to get the compiler to infer the constraints
/// represented by the Parallel. Ideally this could simply be stated as Parallel<S1, S2>.
struct Pair<S1, S2> where S1 == Parallel<S1, S2>.First {
let first: S1
let second: S2
}
print(Pair(first: [1, 2], second: [1, 2] as Set))
// As with bar, the compiler unfortunately allows this
print(Pair(first: [1, 2], second: ["1", "2"] as Set))
// Again, unfortunately the compiler allows this
print(Pair(first: 42, second: 43))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment