Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of declaring typealias for generic type parameter

Tags:

generics

swift

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?

like image 565
Dalija Prasnikar Avatar asked Jun 25 '19 11:06

Dalija Prasnikar


3 Answers

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
like image 50
Stefan Glienke Avatar answered Oct 23 '22 17:10

Stefan Glienke


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.

like image 20
Joanna Carter Avatar answered Oct 23 '22 19:10

Joanna Carter


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.

like image 44
appalling22 Avatar answered Oct 23 '22 17:10

appalling22