imm_copy(imm, a=b, c=d...) yields a persisent copy of the immutable object imm that differs from
    imm only in that the parameters a, c, etc. have been changed to have the values b, d, etc.
    If the object imm is persistent and no changes are made, imm is returned. If imm is transient,
    a persistent copy of imm is always made.

def imm_copy(imm, **kwargs):
    '''
    imm_copy(imm, a=b, c=d...) yields a persisent copy of the immutable object imm that differs from
    imm only in that the parameters a, c, etc. have been changed to have the values b, d, etc.
    If the object imm is persistent and no changes are made, imm is returned. If imm is transient,
    a persistent copy of imm is always made.
    '''
    if not is_imm(imm):
        raise ValueError('Non-immutable given to imm_copy')
    if imm_is_persistent(imm) and len(kwargs) == 0:
        # no changes and copy risk
        return imm
    dup = copy.copy(imm)
    if _imm_is_trans(dup): dup = dup.persist()
    dd = object.__getattribute__(dup, '__dict__')
    if _imm_is_persist(dup):
        # we update values directly then recompute checks and invalidate cache
        all_checks = set([])
        all_deps = set([])
        params = _imm_param_data(imm)
        for (p,v) in six.iteritems(kwargs):
            if p not in params:
                raise ValueError('attempt to set non-parameter \'%s\' in imm_copy()' % p)
            (_, tx_fn, arg_lists, check_fns, deps) = params[p]
            for (arg_list, check_fn) in zip(arg_lists, check_fns):
                all_checks.add((tuple(arg_list), check_fn))
            all_deps |= set(deps)
            dd[p] = v if tx_fn is None else tx_fn(v)
        # now invalidate the deps
        for dep in all_deps:
            if dep in dd:
                del dd[dep]
        # now run the tests
        for (arg_list, check_fn) in all_checks:
            if not check_fn(*[getattr(dup, arg) for arg in arg_list]):
                raise ValueError(
                    'Requirement \'%s%s\' failed when copying immutable' % (check_fn, arg_list))
    else:
        # this is an initial-state immutable...
        for (p,v) in six.iteritems(kwargs): object.__setattr__(dup, p, v)
        _imm_init_to_trans(dup)
        _imm_trans_to_persist(dup)
    return dup