This is very hard to put into words but I've created a minimal example.
Here is a gist if you'd prefer... https://gist.github.com/anonymous/67d83fb2f286cf84539b58be96a971d3
The "data item" protocol
I have a protocol which defines Sortable
objects with a property number
like so.
protocol Sortable: Comparable {
var number: Int {get}
static func < (lhs:Self, rhs: Self) -> Bool
static func == (lhs:Self, rhs: Self) -> Bool
}
struct BasicSortable: Sortable {
let number: Int
static func < (lhs:BasicSortable, rhs: BasicSortable) -> Bool {
return lhs.number < rhs.number
}
static func == (lhs:BasicSortable, rhs: BasicSortable) -> Bool {
return lhs.number == rhs.number
}
}
The "worker" protocol
Then I have a protocol that can do something with these Sortable
types. But because it has a Self requirement it needs to be defined as a protocol with an associated type and in the structs as generic property...
protocol Sorter {
associatedtype Item: Sortable
func sort(items: [Item]) -> [Item]
}
// Two different sorters
struct AscendingSorter<T:Sortable>: Sorter {
typealias Item = T
func sort(items: [T]) -> [T] {
return items.sorted()
}
}
struct DescendingSorter<T:Sortable>: Sorter {
typealias Item = T
func sort(items: [T]) -> [T] {
return items.sorted{$0 > $1}
}
}
The handler
Finally a struct that pulls everything together...
struct DataHandler<T: Sortable> {
let items: [T]
let sortedItems: [T]
init(unsortedItems: [T]) {
items = unsortedItems
let sorter = AscendingSorter<T>()
sortedItems = sorter.sort(items: unsortedItems)
}
}
Making it all work
This all works.
let array = [
BasicSortable(number: 1),
BasicSortable(number: 8),
BasicSortable(number: 13),
BasicSortable(number: 3),
BasicSortable(number: 4),
BasicSortable(number: 14),
BasicSortable(number: 5),
BasicSortable(number: 12),
BasicSortable(number: 3),
]
let handler = DataHandler(unsortedItems: array)
handler.sortedItems
This prints out the array of items in the correct order depending on what type of sorter I create in the Handler
The problem
What I'm trying to do now is to find a property declaration for this sorter
struct that can take ANY Sorter type into it but everything I have tried in doing that has failed so far.
Is there a way to do this?
In the struct I'd like to have...
let sorter: SomeTypeHere
And then in the init set it like...
sorter = AscendingSorter()
but no combination I have tried in doing this has worked.
Thanks
You could use type erasure to implement your own AnySorter
.
Starting with your own code from above:
protocol Sortable: Comparable {
var number: Int {get}
/* as Hamish mentions in his answer:
< and == already blueprinted in Comparable and Equatable */
}
protocol Sorter {
associatedtype Item: Sortable
func sort(items: [Item]) -> [Item]
}
Construct an AnySorter
:
struct AnySorter<Item: Sortable>: Sorter {
private let _sort: ([Item]) -> [Item]
init<S: Sorter where S.Item == Item>(_ sorter: S) {
_sort = sorter.sort
}
func sort(items: [Item]) -> [Item] {
return _sort(items)
}
}
Which you make use of e.g. as an argument to the initializer in your DataHandler
:
struct DataHandler<T: Sortable> {
let items: [T]
let sortedItems: [T]
init(unsortedItems: [T], sorter: AnySorter<T>) {
items = unsortedItems
sortedItems = sorter.sort(items: unsortedItems)
}
}
Your handler can now be used with a type erased AnySorter
applied to your Sortable
types. E.g., for the two simple sorters you've supplied in your question:
struct AscendingSorter<T:Sortable>: Sorter {
typealias Item = T
func sort(items: [T]) -> [T] {
return items.sorted()
}
}
struct DescendingSorter<T:Sortable>: Sorter {
typealias Item = T
func sort(items: [T]) -> [T] {
return items.sorted{$0 > $1}
}
}
/* example usage */
extension Int: Sortable {
var number: Int { return self }
}
let arr = [1, 4, 2, 8, 3]
let dataHandlerDesc = DataHandler(unsortedItems: arr, sorter: AnySorter(DescendingSorter()))
print(dataHandlerDesc.sortedItems) // [8, 4, 3, 2, 1]
let dataHandlerAsc = DataHandler(unsortedItems: arr, sorter: AnySorter(AscendingSorter()))
print(dataHandlerAsc.sortedItems) // [1, 2, 3, 4, 8]
Edit addition to answer your comment:
Is it possible to take the input parameter and store it in a property? Would I just use
AnySorter<T>
as the type of the property?
Yes, you can keep a property in DataHandler
with type AnySorter
. E.g., for a contrived example, we can let sortedItems
be a computed property that makes use of an AnySorter
instance to sort a stored list of items (of course in reality we don't want to do this re-sorting for each call, but for this example only!):
struct DataHandler<T: Sortable> {
let items: [T]
var sortedItems: [T] { return sorter.sort(items: items) }
var sorter: AnySorter<T>
init(unsortedItems: [T], sorter: AnySorter<T>) {
items = unsortedItems
self.sorter = sorter
}
mutating func changeSorter(newSorter: AnySorter<T>) {
sorter = newSorter
}
}
/* example usage */
extension Int: Sortable {
var number: Int { return self }
}
let arr = [1, 4, 2, 8, 3]
var dataHandler = DataHandler(unsortedItems: arr, sorter: AnySorter(DescendingSorter()))
print(dataHandler.sortedItems) // [8, 4, 3, 2, 1]
dataHandler.changeSorter(newSorter: AnySorter(AscendingSorter()))
print(dataHandler.sortedItems) // [1, 2, 3, 4, 8]
If an instance of a given type that conforms to Sorter
can deal with any homogenous array of elements that conform to Sortable
(if it's restricted to a single concrete type, then @dfri's answer has got you covered) – then Sorter
need not have an associatedtype
in the first place. You could simply make the sort(items:)
method generic instead, which would allow you to use Sorter
as a type.
Also if your sort(items:)
method doesn't utilise any instance state (it doesn't in your example code), then you could make it static
– and simply pass around the types of sorters, instead of instances.
For example, your Sortable
protocol, and BasicSortable
implementation:
protocol Sortable : Comparable {
var number : Int { get }
// note that you don't need to re-define the < and == operator requirements,
// as they're already defined by Comparable and Equatable
}
struct BasicSortable : Sortable {
let number : Int
static func < (lhs:BasicSortable, rhs: BasicSortable) -> Bool {
return lhs.number < rhs.number
}
static func == (lhs:BasicSortable, rhs: BasicSortable) -> Bool {
return lhs.number == rhs.number
}
}
Your Sorter
protocol, and different sorter implementations:
protocol Sorter {
// A sort function that can take any homogenous array of a given
// Sortable element (meaning that an instance of a type that conforms to
// Sorter isn't restricted to a single concrete type of Sortable).
// As the function doesn't rely on any instance state, it's static.
static func sort<T:Sortable>(items: [T]) -> [T]
}
// Two different sorters
enum AscendingSorter : Sorter {
static func sort<T:Sortable>(items: [T]) -> [T] {
return items.sorted(by: <)
}
}
enum DescendingSorter : Sorter {
static func sort<T:Sortable>(items: [T]) -> [T] {
return items.sorted(by: >)
}
}
And finally, your DataHandler
with an example usage:
struct DataHandler<T: Sortable> {
let items: [T]
private(set) var sortedItems: [T]
var sorter : Sorter.Type { // simply hold a given type of sorter
willSet {
if sorter != newValue {
// re-sort items upon (different) sorter being set
sortedItems = newValue.sort(items: items)
}
}
}
init(unsortedItems: [T], sorter: Sorter.Type) {
items = unsortedItems
self.sorter = sorter
sortedItems = sorter.sort(items: unsortedItems)
}
}
let items = [BasicSortable(number: 2), BasicSortable(number: 4), BasicSortable(number: 6),
BasicSortable(number: 1), BasicSortable(number: 4)]
var handler = DataHandler(unsortedItems: items, sorter: AscendingSorter.self)
print(handler.sortedItems)
// [BasicSortable(number: 1), BasicSortable(number: 2), BasicSortable(number: 4),
// BasicSortable(number: 4), BasicSortable(number: 6)]
handler.sorter = DescendingSorter.self
print(handler.sortedItems)
// [BasicSortable(number: 6), BasicSortable(number: 4), BasicSortable(number: 4),
// BasicSortable(number: 2), BasicSortable(number: 1)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With