2

I'd like a generic function to merge two dictionaries. It needs to be more all-purpose then the one described in How do you add a Dictionary of items into another Dictionary. Reason: I have objects in my dictionaries, and I only want to update a specific property as opposed to the whole object itself. (To be concrete, they are objects with an array property. I need to either append to this array if the object exists or create a new object with a new array. I can't simply inspect and accordingly overwrite the whole object. I'm interested in it's property.)

I tried to do this using functional programming:

extension Dictionary {
    func merge(withDictionary: Dictionary) -> Dictionary {
        var returnDictionary = withDictionary   // make a copy of dictionary (a dictionary is a struct in Swift)
        // Merge self dictionary into returnDictionary
        for key in self.keys {
            // If there is already a value associated for this key. (In my concrete case I will need to append to the value object's list property.)
            if let withDictionaryValue = returnDictionary[key], selfValue = self[key] {

                // I NEED TO DO THIS HERE.
                //
                // returnDictionary[key]!.list = withDictionaryValue.list + selfValue.list
                //
                // CAN'T FIGURE OUT HOW TO DO THIS IN A GENERIC WAY. THIS GENERIC MERGE SHOULDN'T NEED TO KNOW THAT THIS PARTICULAR DICTIONARY HAS VALUE OBJECTS THAT CONTAIN A 'list' PROPERTY.
                // HOW DO I PASS THIS CODE SNIPPET LINE IN AS PART OF A CLOSURE, WHICH USES 'withDictionaryValue' AND 'selfValue'?

            } else {
                // Simply write into this key - it doesn't yet contain values.
                returnDictionary[key] = self[key]
            }
        }

        return returnDictionary

    }

}
Community
  • 1
  • 1
Daniel
  • 3,758
  • 3
  • 22
  • 43

1 Answers1

3

This generic merge shouldn't need to know that this particular dictionary has value objects that contain a 'list' property.

On the contrary, in order for your function to access the list property on the dictionary's values, you need to tell the compiler that the values have a list property. You can do this by creating a protocol and constraining the extension to only operate on dictionaries that have values that conform to this protocol:

// your protocol that defines the list property
protocol ListType {
    var list : [AnyObject] { get set }
}

extension Dictionary where Value : ListType {
    func merge(withDictionary: Dictionary) -> Dictionary {
        var returnDictionary = withDictionary   // make a copy of dictionary (a dictionary is a struct in Swift)

        for (key, value) in self { // iterate through key value pairs
            if let withDictionaryValue = returnDictionary[key] { // if value exists, merge the list properties
                returnDictionary[key]!.list = value.list + withDictionaryValue.list
            } else {
                returnDictionary[key] = value
            }
        }
        return returnDictionary
    }
}

You can then simply conform the value types you're using to this protocol either directly, or through an extension.

struct Foo : ListType {
    var list: [AnyObject]
}

let d = ["foo" : Foo(list: ["foo", "bar", "baz"])]
let d1 = ["foo" : Foo(list: ["qux", "blah", "blue"])]

let r = d.merge(d1) // ["foo": Foo(list: [foo, bar, baz, qux, blah, blue])]

If you want a more general approach, you could define a Mergable protocol that defines a method where the conforming type can do their own merging logic. You would then change your extension to call this method if the values conform to the protocol, else just merge the key-value pairs.

protocol Mergable {
    func merge(withOther:Self) -> Self
}

// if values are Mergable (and the key-value pairs exist in both dictionaries), then call their custom logic for merging
extension Dictionary where Value : Mergable {
    func merge(withDictionary: Dictionary) -> Dictionary {
        var returnDictionary = withDictionary
        for (key, value) in self {
            if let withDictionaryValue = withDictionary[key] {
                returnDictionary[key] = value.merge(withDictionaryValue)
            } else {
                returnDictionary[key] = value
            }
        }
        return returnDictionary
    }
}

// standard merging logic
extension Dictionary {
    func merge(withDictionary: Dictionary) -> Dictionary {
        var returnDictionary = withDictionary
        keys.forEach {returnDictionary[$0] = self[$0]}
        return returnDictionary
    }
}

You could then conform your value type to this protocol like so:

// Foo's custom merging logic
extension Foo : Mergable {
    func merge(withOther: Foo) -> Foo {
        var merged = self

        // merge the list array
        merged.list.appendContentsOf(withOther.list)

        return merged
    }
}

If your dictionary's values are Mergable, then the compiler will favour the extension that does the custom merging logic, as the more type specific signature is preferred.


In reply to your comment about wanting to do this with closures, you could add a closure argument to the merge function that will pass a pseudo reference to the first value (through using inout), along with the second value, allowing you to mutate the first value when you invoke the function.

extension Dictionary {
    func merge(withDictionary: Dictionary, @noescape merge: (value: inout Value, withValue: Value) -> ()) -> Dictionary {
        var returnDictionary = withDictionary   // make a copy of dictionary (a dictionary is a struct in Swift)

        for (key, value) in self { // iterate through key value pairs
            if let withDictionaryValue = returnDictionary[key] {

                // create mutable copy of the value
                var value = value
                merge(value:&value, withValue:withDictionaryValue) // invoke closure to write merging changes to the value
                returnDictionary[key] = value // assign value

            } else {
                returnDictionary[key] = value
            }
        }
        return returnDictionary
    }
}

// call merge with our own custom merging logic when a given key exists in both dictionaries
let result = dictA.merge(dictB) {$0.list.appendContentsOf($1.list)}

Although this only really makes sense if your merging logic changes each time you call the function, which I suspect is not what you're doing.

If you want your merging logic to remain constant, then this better done with protocols. With closures you're going to have to pass in your merging logic every time you want to invoke the function. With protocols, you just define that logic once – and it's invoked when needed.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • thanks! Is there a more functional programming approach to this? Using closures? That's what I'd like to follow as opposed to the protocol-based approach. (Don't want to start mixing the paradigms in my code...) – Daniel Jun 10 '16 at 07:07
  • @Daniel I have updated my answer showing how you could do it this way, although IMO protocols are a nicer way of doing this. Using closures, you're going to have to pass in your custom logic every time you call the function, which isn't ideal. Protocols allow you to define this logic once, and call it when it's needed. – Hamish Jun 10 '16 at 07:21
  • Great answer, thanks! One question - why do you need to do "var returnValue = $0" when you call merge() in the functional programming solution? – Daniel Jun 10 '16 at 08:28
  • @Daniel Happy to help :) The reason we have to do `var returnValue = $0` is the same reason that you have to do `var returnDictionary = withDictionary` in your original function – the function input is immutable, so we have to create a mutable copy of it before we can append to the array (as changing a property of a value type is a mutation of that value type). – Hamish Jun 10 '16 at 08:48
  • @Daniel Actually thinking about it, you could use an `inout` parameter in your closure, allowing you to make the mutation directly. I have updated my answer showing this. – Hamish Jun 10 '16 at 09:06
  • I don't think that would be very nice, actually. Functional programming is all about working with immutable objects. This is where its power comes from - debugging is a lot easier, parallel computing is easier, etc. – Daniel Jun 10 '16 at 09:11
  • @Daniel Technically as we're still using value types here (and `@noescape` closures), that safety is maintained. An `inout` argument isn't *actually* a reference to a value type (it's easier to think of it like that though), it's only a parameter that the function makes an implicit shadow copy of, which is then written back to the caller's value when the function exits – which is exactly what the original function was doing, just explicitly. Using `inout` here makes the call more expressive. – Hamish Jun 10 '16 at 09:59