docs: 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