Immutable merge ``a`` structure with ``b`` using binary operator ``op``
    on leaf nodes. All nodes at, or below, ``max_depth`` are considered to be
    leaf nodes.

    Merged structure is returned, input data structures are not modified.

    If ``recurse_list=True``, leaf lists of equal length will be merged on a
    list-element level. Lists are considered to be leaf nodes by default
    (``recurse_list=False``), and they are merged with user-provided ``op``.
    Note the difference::

        merge([1, 2], [3, 4]) ==> [1, 2, 3, 4]

        merge([1, 2], [3, 4], recurse_list=True) ==> [4, 6]

def merge(a, b, op=None, recurse_list=False, max_depth=None):
    """Immutable merge ``a`` structure with ``b`` using binary operator ``op``
    on leaf nodes. All nodes at, or below, ``max_depth`` are considered to be
    leaf nodes.

    Merged structure is returned, input data structures are not modified.

    If ``recurse_list=True``, leaf lists of equal length will be merged on a
    list-element level. Lists are considered to be leaf nodes by default
    (``recurse_list=False``), and they are merged with user-provided ``op``.
    Note the difference::

        merge([1, 2], [3, 4]) ==> [1, 2, 3, 4]

        merge([1, 2], [3, 4], recurse_list=True) ==> [4, 6]

    """

    if op is None:
        op = operator.add

    if max_depth is not None:
        if max_depth < 1:
            return op(a, b)
        else:
            max_depth -= 1

    if isinstance(a, dict) and isinstance(b, dict):
        result = {}
        for key in set(chain(a.keys(), b.keys())):
            if key in a and key in b:
                result[key] = merge(a[key], b[key],
                                    op=op, recurse_list=recurse_list,
                                    max_depth=max_depth)
            elif key in a:
                result[key] = deepcopy(a[key])
            elif key in b:
                result[key] = deepcopy(b[key])
        return result

    elif isinstance(a, list) and isinstance(b, list):
        if recurse_list and len(a) == len(b):
            # merge subelements
            result = []
            for idx in range(len(a)):
                result.append(merge(a[idx], b[idx],
                                    op=op, recurse_list=recurse_list,
                                    max_depth=max_depth))
            return result
        else:
            # merge lists
            return op(a, b)

    # all other merge ops should be handled by ``op``.
    # default ``operator.add`` will handle addition of numeric types, but fail
    # with TypeError for incompatible types (eg. str + None, etc.)
    return op(a, b)