:param m_arg: value to match against or callable
    :param arg: arg to match
    :param comparator:  function that returns True if m_arg and arg match
    :param default: will be returned if m_arg is None

    if m_arg is a callable it will be called with arg

    >>> arg_match(1, 1)
    True

    >>> arg_match(1, 2)
    True

    You can match by sub args by passing in a dict

    >>> from collections import namedtuple
    >>> Msg = namedtuple("msg", ["note", "type"])
    >>> m = Msg(note=1, type="note_on")
    >>> arg_match(dict(note=1), m)
    True

def arg_match(m_arg, arg, comparator=eq, default=False):
    """
    :param m_arg: value to match against or callable
    :param arg: arg to match
    :param comparator:  function that returns True if m_arg and arg match
    :param default: will be returned if m_arg is None

    if m_arg is a callable it will be called with arg

    >>> arg_match(1, 1)
    True

    >>> arg_match(1, 2)
    True

    You can match by sub args by passing in a dict

    >>> from collections import namedtuple
    >>> Msg = namedtuple("msg", ["note", "type"])
    >>> m = Msg(note=1, type="note_on")
    >>> arg_match(dict(note=1), m)
    True
    """
    if m_arg is None:
        return default
    if isinstance(m_arg, dict):
        for name, value in m_arg.items():
            name, _comparator = arg_comparitor(name)
            subarg = getattr(arg, name, InvalidArg)
            if subarg is InvalidArg:
                return subarg
            matched = arg_match(subarg, value, _comparator, default)
            if not matched or matched is InvalidArg:
                return False
        return True
    else:
        if hasattr(m_arg, "__call__"):
            return m_arg(arg)
        else:
            return comparator(arg, m_arg)