英文:
How to do type-safe indices in Swift?
问题
I understand you want a translation for the code-related portion. Here it is:
我试图做类似于这样的事情:
typealias HumanId = Int
typealias RobotId = Int
func getHuman(at index: HumanId) -> Human
func getRobot(at index: RobotId) -> Robot
但是现在我可以使用`RobotId`正常调用`getHuman`:`getHuman(at: RobotId(0))`。
如何使这个类型安全?
我明白我可以这样做:
struct HumanId { let id: Int }
struct RobotId { let id: Int }
...并且一些额外的东西让这些结构体作为索引运作,但这将导致一些代码重复,而且由于我有不止2种这样的id类型,我希望以某种方式缩短它,也许使用类型别名和泛型来使它们唯一?
英文:
I'm trying to do something like this:
typealias HumanId = Int
typealias RobotId = Int
func getHuman(at index: HumanId) -> Human
func getRobot(at index: RobotId) -> Robot
but as it is now I can call getHuman
with RobotId
just fine: getHuman(at: RobotId(0))
.
How do I make this typesafe?
I understand that I can do something like:
struct HumanId { let id: Int }
struct RobotId { let id: Int }
...and some extra things to make these structs function as indices, but that would lead to some code duplication, and since I'm having more than 2 of these id-types I would like to shorten this somehow, with typealiases and generics perhaps in order to make them unique?
答案1
得分: 6
你可以利用Swift的泛型来实现你的目标。定义一个泛型的Index
类型,代码如下:
struct Index<T>: RawRepresentable {
let rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
init(_ rawValue: Int) { self.rawValue = rawValue }
}
然后可以像这样使用它:
func getHuman(at index: Index<Human>) -> Human { ... }
func getRobot(at index: Index<Robot>) -> Robot { ... }
getHuman(at: Index(1))
getRobot(at: Index(2))
文字索引
你甚至可以使用ExpressibleByIntegerLiteral
协议为文字索引提供一些语法糖:
extension Index: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) { self.rawValue = value }
}
例如:
getHuman(at: 1)
getRobot(at: 2)
但是下面的代码将无法构建,所以解决方案仍然是类型安全的:
let someIndex = 123
getHuman(at: someIndex)
错误: 无法将类型为'int'的值转换为预期的参数类型'Index<Human>'
可比较的索引
如评论中建议的,我们还可以添加Comparable
遵守(例如,这样你可以将Index
结构体用作遵守标准Collection
协议的类型的索引):
extension Index: Comparable {
static func < (lhs: Index, rhs: Index) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
示例:
Index<Human>(1) < Index<Human>(2) // true
英文:
You could leverage Swift generics to achieve your goal. Introduce a generic Index
type like this:
struct Index<T>: RawRepresentable {
let rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
init(_ rawValue: Int) { self.rawValue = rawValue }
}
and then use it like this:
func getHuman(at index: Index<Human>) -> Human { ... }
func getRobot(at index: Index<Robot>) -> Robot { ... }
getHuman(at: Index(1))
getRobot(at: Index(2))
Literal Indices
You could even use the ExpressibleByIntegerLiteral
protocol to provide some syntax sugar when using literal indices:
extension Index: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) { self.rawValue = value }
}
For instance:
getHuman(at: 1)
getRobot(at: 2)
But the following code will not build, so the solution is still typesafe-ish:
let someIndex = 123
getHuman(at: someIndex)
> error: cannot convert value of type 'Int' to expected argument type 'Index<Human>'
Comparable Indices
As suggested in the comments, we could also add Comparable
conformance as well (e.g., so you can use the Index
struct as the index in a type conforming to the standard Collection
protocol):
extension Index: Comparable {
static func < (lhs: Index, rhs: Index) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
Example:
Index<Human>(1) < Index<Human>(2) // true
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论