-
Notifications
You must be signed in to change notification settings - Fork 49
Add equivalency protocols and implementations (1/3) #568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
a63d20d
0e78167
8a39fce
deb85cb
1c28569
54a8a25
d4418a8
ebcb1b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,4 +26,88 @@ public protocol EnvironmentKey { | |
| /// The default value that will be vended by an `Environment` for this key if no other value | ||
| /// has been set. | ||
| static var defaultValue: Self.Value { get } | ||
|
|
||
|
|
||
| /// Compares two environment values without direct conformance of the values. | ||
| /// - Parameters: | ||
| /// - lhs: The left hand side value being compared. | ||
| /// - rhs: The right hand side value being compared. | ||
| /// - context: The context to evaluate the equivalency. | ||
| /// - Returns: Whether or not the two values are equivalent in the specified context. | ||
| static func isEquivalent(lhs: Value, rhs: Value, in context: EquivalencyContext) -> Bool | ||
|
||
|
|
||
| } | ||
|
|
||
| extension EnvironmentKey where Value: Equatable { | ||
|
|
||
| public static func isEquivalent(lhs: Value, rhs: Value, in context: EquivalencyContext) -> Bool { | ||
| lhs == rhs | ||
| } | ||
|
|
||
| /// Convenience implementation returning that the values are always equivalent in the specified contexts, and otherwise evaluates using Equality. | ||
| /// - Parameters: | ||
| /// - contexts: Contexts in which to always return true for equivalency. | ||
| /// - lhs: The left hand side value being compared. | ||
| /// - rhs: The right hand side value being compared. | ||
| /// - evaluatingContext: The context in which the values are currently being compared. | ||
| /// - Returns: Whether or not the two values are equivalent in the specified context. | ||
| /// - Note: This is often used for convenience in cases where layout is unaffected, e.g., for an environment value like dark mode, which will have no effect on internal or external layout. | ||
| public static func alwaysEquivalentIn( | ||
| _ contexts: Set<EquivalencyContext>, | ||
| lhs: Value, | ||
| rhs: Value, | ||
| evaluatingContext: EquivalencyContext | ||
| ) -> Bool { | ||
| if contexts.contains(evaluatingContext) { | ||
| true | ||
| } else { | ||
| lhs == rhs | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| extension EnvironmentKey where Value: ContextuallyEquivalent { | ||
|
|
||
| public static func isEquivalent(lhs: Value, rhs: Value, in context: EquivalencyContext) -> Bool { | ||
| lhs.isEquivalent(to: rhs, in: context) | ||
| } | ||
|
|
||
| /// Convenience implementation returning that the values are always equivalent in the specified contexts, and otherwise evaluates using ContextuallyEquivalent. | ||
| /// - Parameters: | ||
| /// - contexts: Contexts in which to always return true for equivalency. | ||
| /// - lhs: The left hand side value being compared. | ||
| /// - rhs: The right hand side value being compared. | ||
| /// - evaluatingContext: The context in which the values are currently being compared. | ||
| /// - Returns: Whether or not the two values are equivalent in the specified context. | ||
| /// - Note: This is often used for convenience in cases where layout is unaffected, e.g., for an environment value like dark mode, which will have no effect on internal or external layout. | ||
| public static func alwaysEquivalentIn( | ||
| _ contexts: Set<EquivalencyContext>, | ||
| lhs: Value, | ||
| rhs: Value, | ||
| evaluatingContext: EquivalencyContext | ||
| ) -> Bool { | ||
| if contexts.contains(evaluatingContext) { | ||
| true | ||
| } else { | ||
| lhs.isEquivalent(to: rhs, in: evaluatingContext) | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| extension EnvironmentKey { | ||
|
|
||
| /// Convenience comparison to express default equality in specific contexts. | ||
| /// - Parameters: | ||
| /// - contexts: The contexts in which the values are always equilvalent. | ||
| /// - evaluatingContext: The context being evaulated. | ||
| /// - Returns: Whether or not the value is equivalent in the context. | ||
| public static func alwaysEquivalentIn( | ||
| _ contexts: Set<EquivalencyContext>, | ||
| evaluatingContext: EquivalencyContext | ||
| ) -> Bool { | ||
| contexts.contains(evaluatingContext) | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import Foundation | ||
|
|
||
| // A context in which to evaluate whether or not two values are equivalent. | ||
| public enum EquivalencyContext: Hashable, Sendable, CaseIterable { | ||
|
|
||
| /// The two values are identicial in every respect that could affect displayed output. | ||
| case all | ||
|
|
||
| // More fine-grained contexts: | ||
|
|
||
| /// The two values are equivalent in all aspects that would affect the size of the element. | ||
| /// - Warning:Non-obvious things may affect element-sizing – for example, setting a time zone may seem like something that would only affect date calculations, but can result in different text being displayed, and therefore affect sizing. Consider carefully whether you are truly affecting sizing or not. | ||
| case elementSizing | ||
| } | ||
|
|
||
| public protocol ContextuallyEquivalent { | ||
|
||
|
|
||
| /// Allows a type to express equivilancy within certain contexts. For example, an Environment that represents dark mode would be equivalent to an Environment that represents light mode in a `elementSizing` context, but not in `all` contexts. | ||
| /// - Parameters: | ||
| /// - other: The instance of the type being compared against. | ||
| /// - context: The context to compare within. | ||
| /// - Returns: Whether or not the other instance is equivalent in the specified context. | ||
| /// - Note: Equivilancy within a given context is transitive – that is, if value A is equivalent to value B in a given context, and B is equivalent to C in that same context, A will be considered equivalent to C with that context. | ||
| func isEquivalent(to other: Self?, in context: EquivalencyContext) -> Bool | ||
|
|
||
| } | ||
|
|
||
| extension ContextuallyEquivalent { | ||
|
|
||
| /// Convenience equivalency check passing in .all for context. | ||
| /// - other: The instance of the type being compared against. | ||
| /// - Returns: Whether or not the other instance is equivalent in all contexts. | ||
| public func isEquivalent(to other: Self?) -> Bool { | ||
| isEquivalent(to: other, in: .all) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| extension ContextuallyEquivalent { | ||
|
|
||
| // Allows comparison between types which may or may not be equivalent. | ||
| @_disfavoredOverload | ||
| public func isEquivalent(to other: (any ContextuallyEquivalent)?, in context: EquivalencyContext) -> Bool { | ||
| isEquivalent(to: other as? Self, in: context) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| // Default implementation that always returns strict equivalency. | ||
| extension ContextuallyEquivalent where Self: Equatable { | ||
|
|
||
| public func isEquivalent(to other: Self?, in context: EquivalencyContext) -> Bool { | ||
| self == other | ||
| } | ||
|
|
||
| } | ||
|
|
||
| public struct AnyContextuallyEquivalent: ContextuallyEquivalent { | ||
|
|
||
| let base: Any | ||
|
|
||
| public init(_ value: some ContextuallyEquivalent) { | ||
| base = value | ||
| } | ||
|
|
||
| public func isEquivalent(to other: AnyContextuallyEquivalent?, in context: EquivalencyContext) -> Bool { | ||
| guard let base = (base as? any ContextuallyEquivalent) else { return false } | ||
| return base.isEquivalent(to: other?.base as? ContextuallyEquivalent, in: context) | ||
| } | ||
|
|
||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spec doc does a nice job outlining that internal key/values are "hidden from consumers and not considered in equivalency checks." Do you think we could carry that into docs here, to outline the differences between these two dictionaries?