toejough / dictmerge

merge python dictionaries
MIT License
0 stars 1 forks source link

Add support for associating merge functions with custom ducktyping #3

Closed toejough closed 9 years ago

toejough commented 9 years ago

It should be possible to allow the user to say "if thing1 quacks and thing2 barks, use the flying-dog merge function". This is even more generic than #1 or #2, and is probably the right way to implement the extend/override functionality.

toejough commented 9 years ago

Implementation ideas:

It's not possible to guarantee that given an input, only one tester will match. The best we can do is allow the user to reorder the tester, and obey that order.

In the same way, it cannot be guaranteed that an input will match a tester in the list. There needs to be a default type (there is now - 'scalar' - but this property needs to be maintained).

This allows complete control over merge semantics, as well as keeping merges deterministic and reasonable.

While it's tempting to allow functions to return the types, so that a single function (such as lambda x: type(x).__name__ for most things) can cover more types, and therefor be more efficient, this must be avoided. Testers need to be predicates, and will be treated as such. If not, then the type->tester association is meaningless, and the ease of reasoning goes out the window.

The type-to-tester array will look something like this:

testers_by_type = [
  {'type': 'foo', 'tester': is_foo},
  {'type': 'bar', 'tester': is_bar},
  {'type': 'baz', 'tester': is_baz},
  {'type': 'flying-dog', 'tester': is_dragonfly},  # remember, the point is to allow customization and control.
]

The types-to-merge array doesn't need to be ordered, so we can make it a dictionary. It will probably look something like this:

mergers_by_types['foo']['bar'] = merge_foo_bar
mergers_by_types['foo']['baz'] = merge_foo_baz
mergers_by_types['bar']['foo'] = merge_to_turducken  # no need to make merge functions commutative

I say in the comments there's no reason to make the merge functions commutative - for the sake of ease of use, the API to add a custom function should allow you to provide a flag indicating commutativity...probably defaulting to True (merge(foo, baz) == merge(baz, foo)).

toejough commented 9 years ago

Expected user API:

m = pymerge.Merge()
m.types.add('foo', is_foo)  # add throws if pre-existing, adds to top of list
m.types.replace('foo', is_foo)  # replace throws if not pre-existing, replaces in place
m.types.update('foo', is_foo)  # adds or replaces, no throw
m.types.list()  # returns [('foo', is_foo)]
m.types.order(['foo', 'bar'])  # re-orders the types list so that foo is checked first, then bar, then whatever else is in the list

m.mergers.add('foo', 'bar', foo_bar_merge, commutative=False)
# also provide replace/update.  commutative defaults to true