【问题标题】:Converting mutable struct to immutable in Swift在 Swift 中将可变结构转换为不可变结构
【发布时间】:2017-06-11 11:03:49
【问题描述】:

我进行了很多搜索,阅读了函数式编程,但似乎无法为我的问题找到一个直接的答案。我正在用 Swift 创建一个游戏实用程序库,并且我了解限制可变性的概念,但我正在努力为简单的 PRNG 找到一个好的实现。如果我有以下代码

public struct SplitMix64 {
    var seed: UInt64

    public mutating func nextUInt64() -> UInt64 {
        seed = seed &+ 0x9E3779B97F4A7C15
        var z: UInt64 = (seed ^ (seed >> 30)) &* 0xBF58476D1CE4E5B9
        z = (z ^ (z >> 27)) &* 0x94D049BB133111EB
        return z ^ (z >> 31)
    }
}

这可行,但如果我必须将它作为函数参数传递,则会导致问题,要么我必须在任何地方使用 inout 装饰(通过引用传递值),这意味着其他结构函数会发生变异(它像病毒一样通过您的代码)并在您想使用变异函数但不关心更新的状态时会导致编译器错误,如果将其传递给没有 inout let random=SplitMix64(SplitMix64(1).nextUInt64()) 的函数,则会将其丢弃。

我知道我可以以更实用的方式实现它,例如

    public static func nextUInt64(state: UInt64) -> (state: UInt64, value: UInt64) {
        let newState = state &+ 0x9E3779B97F4A7C15
        var z: UInt64 = (newState ^ (newState >> 30)) &* 0xBF58476D1CE4E5B9
        z = (z ^ (z >> 27)) &* 0x94D049BB133111EB
        return (state: newState, value: z ^ (z >> 31))
    }

但这只会给我的库的用户带来更多问题,而不是调用函数并获取值,他们现在负责跟踪状态,将其传入,并在返回时将其存储并提取从元组生成的数字。而这只是一个简单的函数,其他的生成器有更多的状态数据。我也可以像这样在状态上使用 inout 来保存元组返回

public static func nextUInt64(state: inout UInt64) -> UInt64 {
    state = state &+ 0x9E3779B97F4A7C15
    var z: UInt64 = (state ^ (state >> 30)) &* 0xBF58476D1CE4E5B9
    z = (z ^ (z >> 27)) &* 0x94D049BB133111EB
    return z ^ (z >> 31)
}

但是,当从外部调用时,发生的事情可能并不明显 例如let rand=nextUInt64(&currentState),因为只有 & 告诉您它可能会更新 currentState,并且仍然需要用户跟踪变异值,所以我不确定它是否是一个干净的设计。

当然,在这种情况下,我可以只使用一个类,但是这样我就失去了 Swift 中值类型的好处,而且大多数引擎都使用具有值类型的数组——当你有来自值结构到类。

我的问题是 - 有没有更好的方法在 Swift 中实现它 - 我知道它不是一种函数式语言,而且我对 OOP 没有任何问题。任何想法/指针都非常感谢!

【问题讨论】:

    标签: swift function oop design-patterns functional-programming


    【解决方案1】:

    如果您将 (!) 方法拆分为两个单独的部分:

    • 一个next() 方法返回一个新的SplitMix64 值和更新的种子, 和
    • value 属性返回随机数,

    然后你可以创建随机数而不改变值:

    public struct SplitMix64 {
        let seed: UInt64
    
        public func next() -> SplitMix64 {
            return SplitMix64(seed: seed &+ 0x9E3779B97F4A7C15)
        }
    
        public var value: UInt64 {
            var z: UInt64 = (seed ^ (seed >> 30)) &* 0xBF58476D1CE4E5B9
            z = (z ^ (z >> 27)) &* 0x94D049BB133111EB
            return z ^ (z >> 31)
        }
    }
    
    let random = SplitMix64(seed: 1).next().next().value
    

    如果您不关心更新后的状态。 如果你在乎 那么您仍然可以传递SplitMix64 值,如inout 表达。

    【讨论】:

    • 感谢您的快速回复,这非常干净且实用 - 我读过拆分功能很好,但直到您的示例才清楚了解:)。我必须多考虑一下,还有一些更复杂的例子,比如 Xoroshiro128Plus,它从当前状态生成值,然后改变当前状态(类似于 SplitMix64 的反转)。如此现实地,将 C 版本与我编写的版本(如您的示例)进行比较,我会领先一个 - .next().value 将在序列中获得值 2。
    猜你喜欢
    • 1970-01-01
    • 2016-10-17
    • 1970-01-01
    • 2019-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多