类型别名的目的

Purpose of typealias

我以为今天我终于明白什么是typealias了。

我没有。

我们来看一个例子:

typealias Graph = [String: [String]]

let futurama: Graph = [
    "you": ["bender", "hermes", "scruffy"],
    "bender": ["hubert", "zoidberh"],
    "hermes": ["hubert", "amy", "scruffy"],
    "hubert": ["mom", "fry"],
    "fry": ["leela"],
    "leela": ["brannigan", "nibbler", "scruffy"],
    "amy": ["kif"],
    "brannigan": ["kif"],
    "zoidberh": [],
    "kif": [],
    "mom": [],
    "nibbler": [],
    "scruffy": []
]

extension Graph {
    // Breadth First Search
    func bfs(from start: String, to finish: String) -> [String]? { 
        // Implementation of this graph algorithm here
    }
}

print(
    futurama.bfs(from: "you", to: "scruffy")?.joined(separator: " --> ") ?? "There is no pass, sorry"
)

一切正常。
然后我做了一个小改动:

let futurama: [String: [String]] = [
        "you": ["bender", "hermes", "scruffy"],
        "bender": ["hubert", "zoidberh"],
        ...

我原以为现在 futurama.bfs() 不会编译,因为 futurama 没有方法 bfs。多么聪明,我在想,多么伟大的语言设计!
但我很失望。没有改变。完全没有。代码仍然可以编译和工作。
所以...

  1. 类型别名有什么用?
  2. 如何实现我期望的行为?
  1. typealias 字面意思是为类型创建“别名”(即另一个名称)。您不是在创建新类型,只是为现有类型创建另一个 name。来自 Language Reference:

    Type aliases do not create new types; they simply allow a name to refer to an existing type.

    因此,一旦您声明了类型别名,Graph[String: [String]] 现在指的是同一类型。 Graph 的扩展等同于 [String: [String]].

    的扩展
  2. 对于您想要的行为,您需要创建一个新类型。对于 Graph,我认为结构是合适的:

    struct Graph {
        ...
    }
    

    Graph 结构然后可以包含(或者,封装)类型为 [String: [String]]

    的私有 属性
    private var adjacencyDictionary: [String: [String]]
    

    并且您应该编写访问和(可选地)改变图的方法。例如getNode(withName:)getNeighboursOfNode(withName:)addNodeaddEdge

    您不应在此处使用类型别名,因为图 不是 [String: [String]]。例如,以下 [String: [String]] 不是(有效的)图形:

    ["a": ["b"]]
    

根据文档(在此处找到:https://docs.swift.org/swift-book/ReferenceManual/Declarations.html):

A type alias declaration introduces a named alias of an existing type into your program.

[...]

After a type alias is declared, the aliased name can be used instead of the existing type everywhere in your program. The existing type can be a named type or a compound type. Type aliases do not create new types; they simply allow a name to refer to an existing type.

所以事实上你并没有定义一个新的类型。您只是用另一个名称为您的类型别名,以使该类型更易于使用。

因此,在您的情况下,Graph 只是类型 [String: [String]] 的另一个别名,不会为您引入新类型。

因此,要实现您的预​​期,我想有很多可能性。一种方法是包装你的 Graph 例如[String: [String]] 到结构或 class 中,并为该结构编写扩展名 / class。实现方式很大程度上取决于您想要实现的目标。

这是否回答了您的问题?

A typealias 只是语法糖。所有类型别名都会在编译开始时被它们别名的实际类型替换,因此类型别名在 type-system 级别不存在。

这就是您所看到的行为背后的原因 - 在类型系统级别,Graph 只是 [String:[String]],因此定义任何使用 Graph 的函数实际上都有效在别名类型上,即 [String:[String]].

如果你想让 Graph 成为一个真正的类型,你需要像这样声明它 - 使它成为 structclass 并带有 [=19 的后备存储=].

struct Graph {
    var storage: [String: [String]]
}

extension Graph {
    // Breadth First Search
    func bfs(from start: String, to finish: String) -> [String]? {
        // Implementation of this graph algorithm here
    }
}

let futurama: [String: [String]] ...
futurama.bfs() // This doesn't compile anymore

根据您在下面的回答,我想出了一个非常巧妙的方法。
这是下标、ExpressibleByDictionaryLiteral 和泛型的组合。

struct Graph<T> where T: Hashable {
    private var storage: [T: [T]]
    
    subscript(key: T) -> [T]! {
        storage[key]
    }
    
    func bfs(from start: T, to finish: T) -> [T]? {
        // Implementation of the algorithm here. 
    }
}

extension Graph: ExpressibleByDictionaryLiteral {

    init(dictionaryLiteral elements: (T, [T])...) {
        storage = .init(uniqueKeysWithValues: elements)
    }
}

我最初 post 中的代码无需任何更改即可编译和工作!
(除了泛型的东西:String 应该到处都变成 T