ModifiedCopyMacro icon indicating copy to clipboard operation
ModifiedCopyMacro copied to clipboard

Idea of implementation for a Kotlin-like copy function

Open MrJorgeXavier opened this issue 4 months ago • 4 comments

Thanks for doing this package, it's very useful!

I saw in the readme that you would like to know some ideas on how to implement the copy function like kotlin does, and I think this issue could act as a thread to discuss how could we approach this.

I have an approach that uses Optional in a way that we can know if the caller wants to put nil in the property or keep the same value as before. However, this approach has a drawback: The required values (not optional) of the struct will still be declared as Optional in the copy method, so the caller could pass nil there, expecting it to nullify the property, but it would keep the same value as before instead (since the property is not optional).


struct MyStruct {
    let strValue: String?
    let intValue: Int

    /// Expanded copy method:
    /// If the type is optional, we assign the default value as `.some(nil)` instead of `nil`, so we can assert the case where the caller wants to put `nil` in the property.
    /// If the type is not optional, we assign the default value as `nil`, so we can know if the user wants to put a new value or keep the old one in the property.
    func copy(
        strValue: String?? = .some(nil),
        intValue: Int? = nil
    ) -> MyStruct {
        return MyStruct(
            strValue: getValue(strValue, currentValue: self.strValue),
            intValue: intValue ?? self.intValue
        )
    }
}

/// This function needs to be in an appropriate place... But I'm not sure where.
fileprivate func getValue<T>(_ doubleOptional: T??, currentValue: T?) -> Optional<T> {
    switch doubleOptional {
    case .none:
        return nil
    case .some(let wrapped):
        switch wrapped {
        case .none: return currentValue
        case .some(let newValue): return newValue
        }
    }
}

func tests() {
        let original = MyStruct(strValue: "Hello", intValue: 20)

        let copy1 = original.copy(strValue: nil, intValue: 10) // outputs strValue nil and intValue 10

        let copy2 = original.copy(strValue: "World") // outputs strValue "World" and intValue 20

        let copy3 = original.copy(intValue: 15) // outputs strValue "Hello" and intValue 15

        let copy4 = original.copy(intValue: nil) // outputs strValue "Hello" and intValue 20. This case is one that should not be possible to call, but unfortunately it is.
}

Let me know your opinions on this matter, I would be glad to open a pull request with this solution if we deemed it good enough.

MrJorgeXavier avatar Feb 16 '24 14:02 MrJorgeXavier