Consider following code:
public struct Foo<E: Hashable>: Equatable, Collection
{
public typealias Element = E
private var array: [Element]
...
}
This kind of coding practice can be found in many places, including official Apple code-bases like https://github.com/apple/swift-package-manager/blob/master/Sources/Basic/OrderedSet.swift
What is the purpose of declaring typealias Element
instead of using Element
directly as generic type parameter name like:
public struct Foo<Element: Hashable>
{
private var array: [Element]
...
}
Is it just some sort of coding preference, or there are some other reasons, like achieving some functionality that could not be made without such simple typealias declarations?
Declaring a public typealias makes it possible to access the generic type parameter outside of the closed generic type.
For example if you declare a typealias WidgetFoo = Foo<Widget>
and keep using WidgetFoo
in other places it will be possible to access its T
via WidgetFoo.Element
(which refers to Widget
) whereas you cannot access the generic type parameter E
itself. This enables robust and refactoring friendly code - imagine you want to replace Widget
with BetterWidget
you only have to change one place (the type alias declaration) and no other because WidgetFoo.Element
will then refer to BetterWidget
.
Example code (provided by @Airspeed Velocity)
struct S<T> { typealias U = T }
typealias TA = S<Int>
let x: TA.T // 'T' is not a member type of 'TA' (aka 'S<Int>')
let y: TA.U
We seem to be talking at cross purposes here.
You cite this code:
public struct Foo<E: Hashable>: Equatable, Collection
{
public typealias Element = E
private var array: [Element]
…
}
… where the generic struct Foo<E : Hashable>
has a placeholder E
(which is required to implement Hashable, in order that any type that is used for E
will be compatible with the Element
parameter declared in the Collection protocol); and the struct Foo<E>
also is required to implement the Collection (generic) protocol.
The typealias Element = E
line is there to bind the E
parameter of the generic struct to the Element
associatedtype from the (generic) Collection protocol; nothing more nothing less. This is the reason I gave you my example of using type aliases to help the compiler to give you the missing "abstract" methods that are needed to fully implement the Collection protocol.
On the other hand, your code:
public struct Foo<Element: Hashable>
{
private var array: [Element]
…
}
… has nothing to do with implementing a protocol; it is simply a generic struct with a placeholder parameter called Element, which is required to implement Hashable.
This time, the struct simply contains an array of whatever Element is used as when you use Foo<Element>
. There is no need for a typealias because there is nothing to bind the Element placeholder to; it serves the same purpose as E in the first example, but without the bit where the struct implements Collection.
I must admit to being puzzled about what @Airspeed Velocity's code:
struct S<T> { typealias U = T }
typealias TA = S<Int>
let x: TA.T // 'T' is not a member type of 'TA' (aka 'S<Int>')
let y: TA.U
… is intended to achieve. Why would you want a typealias U
for the type T
that is already known and available? In many, many years of programming generics, it something I've not yet come across, unless it's another way of writing something else I might have used in C# generics.
Let me replace Swift's own Collection protocol with one just for you Dalija:
protocol DalijaCollection
{
associatedtype ItemType : Hashable // placeholder - does the same as Element from Collection
func append(item: ItemType)
func remove(item: ItemType)
var count: Int { get }
subscript(index: Int) -> ItemType { get }
}
class Stack<itemT : Hashable> : DalijaCollection
{
typealias ItemType = itemT // not needed once all the members of DalijaConnection have been implemented
lazy var items: [itemT] = .init()
func push(item: itemT)
{
items.append(item)
}
func pop() -> itemT?
{
guard let result = items.last,
let lastIndex = items.lastIndex(of: result) else
{
return nil
}
items.remove(at: lastIndex)
return result
}
func append(item: itemT)
{
items.append(item)
}
func remove(item: itemT)
{
guard let index = items.index(of: item) else
{
fatalError("item not found")
}
items.remove(at: index)
}
var count: Int
{
return items.count
}
subscript(index: Int) -> itemT
{
return items[index]
}
}
With the DalijaCollection version, you can do this:
while stack.count > 0
{
print(stack.pop())
}
// or
for i in 0..<stack.count
{
print(stack[i])
}
… Swift's Collection protocol also allows you to use the for…in
construct like this:
{
for item in stack
{
print(item)
}
}
… as well as a whole load more.
typealias
and associatedtype
mean that the values of type parameters can be hidden and become an implementation detail rather than the public interface of a type. This gets rid of an awful lot of noise when defining extensions, properties, methods, referencing superclasses/protocols etc.
Russ Bishop explains this really well with excellent examples here.
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