docs: def itable(*args, **kwargs):
      '''
      itable(...) yields a new immutable table object from the given set of arguments. The arguments
        may be any number of maps or itables followed by any number of keyword arguments. All the
        entries from the arguments and keywords are collapsed left-to-right (respecting laziness),
        and the resulting column set is returned as the itable. Arguments and maps may contain
        values that are functions of zero arguments; these are considered lazy values and are not
        evaluated by the itable function.
      '''
      # a couple things we want to check first... does our argument list reduce to just an empty
      # itable or just a single itable?
      if len(args) == 0 and len(kwargs) == 0:
          return ITable({}, n=0)
      elif len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], ITable):
          return args[0]
      # we want to try to convert any arguments we can from datatables into maps
      try:
          import pandas
          args = [{k:a[k].values for k in a.keys()} if isinstance(a, pandas.DataFrame) else a
                  for a in args]
      except: pass
      # now we want to merge these together and make them one lazy map
      m0 = lazy_map(merge(args, kwargs))
      # see if we can deduce the row size from a non-lazy argument:
      v = next((m0[k] for k in six.iterkeys(m0) if not m0.is_lazy(k)), None) if is_lazy_map(m0) else \
          None
      if v is None:
          k = next((u for u in six.iterkeys(m0)), m0)
          if k is m0: v = None
          else: v = m0[k]
      return ITable(m0, n=(None if v is None else len(v)))