dimanche 11 juin 2017

Converting mutable struct to immutable in Swift

I've searched a lot, read up on functional programming, and can't seem to find a straightforward answer to my problem. I'm creating a game utility library in Swift, and I understand the concepts of limiting mutability, but I'm struggling to find a good implementation for a simple PRNG. If I have the following code

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)
    }
}

This works, but causes problems if I have to pass it as a function parameter, either I have to use the inout decoration everywhere (pass value by reference) and that means other struct functions become mutating (which spreads like a virus through your code) and causes compiler errors when you want to use a mutating func but don't care about the updated state and would throw it away if it's passed into a function without inout let random=SplitMix64(SplitMix64(1).nextUInt64()).

I understand I could implement it in a more functional way such as

    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))
    }

but this just makes more issues for the user of my library, instead of calling the function and getting a value, they are now responsible for having to keep track of state, pass it in, and store it on return and extract the generated number from the tuple. And this is just a simple function, other generators have more state data. I could also use inout on the state to save the tuple return like so

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)
}

but this, when called from the outside may not be obvious whats occurring such as let rand=nextUInt64(&currentState) as only the & tells you that it might update currentState, and still requires the user to track the mutating value so I'm not sure it's a clean design.

Of course, I could in this case, just use a class, but then I lose the benefits of value types in Swift, and most of the engine is using arrays with value types - which then becomes messy when you have links from value struct to classes.

My question is - is there better way to implement this in Swift - I know it isn't a functional language, and I have no issues with OOP. Any thoughts / pointers are much appreciated!

Aucun commentaire:

Enregistrer un commentaire