In Swift, I have a custom struct with this basic premise:
A wrapper struct that can contain any type that conforms to BinaryInteger
such as Int, UInt8, Int16, etc.
protocol SomeTypeProtocol {
associatedtype NumberType
var value: NumberType { get set }
}
struct SomeType<T: BinaryInteger>: SomeTypeProtocol {
typealias NumberType = T
var value: NumberType
}
And an extension on Collection:
extension Collection where Element: SomeTypeProtocol {
var values: [Element.NumberType] {
return self.map { $0.value }
}
}
For example, this works nicely:
let arr = [SomeType(value: 123), SomeType(value: 456)]
// this produces [123, 456] of type [Int] since literals are Int by default
arr.values
What I would like to do is the exact same thing, but for SomeType<T>?
let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]
// this doesn't work, obviously
arr.values
// but what I want is this:
arr.values // to produce [Optional(123), Optional(456)]
I've tried numerous attempts to solve this and a fair amount of research, but I'm hoping any of the sage Swift veterans might shed some light on this.
This is what I envision it might look like, but this doesn't work:
extension Collection where Element == Optional<SomeType<T>> {
var values: [T?] {
return self.map { $0?.value }
}
}
This is a clumsy way of achieving the goal without using generics, and it works:
extension Collection where Element == Optional<SomeType<Int>> {
var values: [Int?] {
return self.map { $0?.value }
}
}
let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]
arr.values // [Optional(123), Optional(456)]
But it requires manually writing extensions for every known type conforming to BinaryInteger, and will not automatically include possible future types adopting BinaryInteger without manually updating the code.
// "..." would contain the var values code from above, copy-and-pasted
extension Collection where Element == Optional<SomeType<Int>> { ... }
extension Collection where Element == Optional<SomeType<Int8>> { ... }
extension Collection where Element == Optional<SomeType<UInt8>> { ... }
extension Collection where Element == Optional<SomeType<Int16>> { ... }
extension Collection where Element == Optional<SomeType<UInt16>> { ... }
extension Collection where Element == Optional<SomeType<Int32>> { ... }
extension Collection where Element == Optional<SomeType<UInt32>> { ... }
extension Collection where Element == Optional<SomeType<Int64>> { ... }
extension Collection where Element == Optional<SomeType<UInt64>> { ... }
EDIT 2018-Jun-23:
Solution #1 - Fully Generic but Must be Func, Not Computed Property
Expanded on Ole's reply:
Pros: If values()
becomes a func
and not a computed property, this is an elegant solution.
Cons: No known way to implement this approach as computed properties, and Swift's Quick Help popup shows [T] and [T?] when inspecting values()
in your code. ie: it just says func values<T>() -> [T] where T : BinaryInteger
which isn't very informative or Swifty. However it still remains strongly typed of course.
extension Collection {
func values<T>() -> [T] where Element == SomeType<T> {
return map { $0.value }
}
func values<T>() -> [T?] where Element == SomeType<T>? {
return map { $0?.value }
}
}
Solution #2 - Optional Protocol Workaround
Expanded on Martin's reply:
Pros: Allows use of computed properties (cleaner for the end-user to access since it doesn't require func parens) and shows inferred type in Xcode's Quick Help popup.
Cons: Not as elegant from an internal code standpoint, as it requires a workaround. But not necessarily a drawback.
// Define OptionalType
protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}
extension Optional: OptionalType {
var asOptional: Wrapped? {
return self
}
}
// Extend Collection
extension Collection where Element: SomeTypeProtocol {
var values: [Element.NumberType] {
return self.map { $0.value }
}
}
extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
var values: [Element.Wrapped.NumberType?] {
return self.map { $0.asOptional?.value }
}
}