docs: def materialize(typ, rel=None, origin=None, unique=None, links=None, inverse=False, split=None, attributes=None):
      '''
      Create a new resource related to the origin
  
      :param typ: IRI of the type for the resource to be materialized,
      which becomes the target of the main link, and the origin of any
      additional links given in the links param
  
      :param rel: IRI of the relationship between the origin and the materialized
      target, or a list of relationship IRIs, each of which will be used to create
      a separate link, or a versa action function to derive this relationship or
      list of relationships at run time, or None. If none, use the action context.
  
      :param origin: Literal IRI or Versa action function for origin of the
      main generated link. If none, use the action context.
  
      :param unique: Versa action function to be invoked in order to
      derive a unique hash key input for the materialized resource, in the form of
      multiple key, value pairs (or key, list-of-values)
  
      :param links: Dictionary of links from the newly materialized resource.
      Each keys can be a relationship IRIs, a Versa action function returning
      a relationship IRI, a Versa action function returning a list of Versa
      contexts, which can be used to guide a sequence pattern of generated
      links, or a Versa action function returning None, which signals that
      the particular link is skipped entirely.
  
      :param postprocess: IRI or list of IRI queueing up actiona to be postprocessed
      for this materialized resource. None, the default, signals no special postprocessing
  
      For examples of all these scenarios see marcpatterns.py
  
      :return: Versa action function to do the actual work
      '''
      links = links or []
      attributes = attributes or {}
      def _materialize(ctx):
          '''
          Inserts at least two main links in the context's output_model, one or more for
          the relationship from the origin to the materialized resource, one for the
          type of the materialized resource, and links according to the links parameter
  
          :param ctx: Runtime Versa context used in processing (e.g. includes the prototype link)
          :return: None
  
          This function is intricate in its use and shifting of Versa context, but the
          intricacies are all designed to make the marcpatterns mini language more natural.
          '''
          #FIXME: Part of the datachef sorting out
          if not ctx.idgen: ctx.idgen = idgen
          _typ = typ(ctx) if callable(typ) else typ
          _rel = rel(ctx) if callable(rel) else rel
          _unique = unique(ctx) if callable(unique) else unique
          (o, r, t, a) = ctx.current_link
          #FIXME: On redesign implement split using function composition instead
          targets = [ sub_t.strip() for sub_t in t.split(split) ] if split else [t]
          #Conversions to make sure we end up with a list of relationships out of it all
          if _rel is None:
              _rel = [r]
          rels = _rel if isinstance(_rel, list) else ([_rel] if _rel else [])
          objids = []
  
          #Botanical analogy
          #The stem is the relationship from the original to the materialized resource 
          #The veins are any further relationships from materialized resource 
          for target in targets:
              ctx_stem = ctx.copy(current_link=(o, r, target, a))
              if origin:
                  #Have been given enough info to derive the origin from context. Ignore origin in current link
                  o = origin(ctx_stem)
  
              computed_unique = [] if _unique else None
              if _unique:
                  # strip None values from computed unique list, including pairs where v is None
                  for k, v in _unique:
                      if None in (k, v): continue
                      v = v if isinstance(v, list) else [v]
                      for subitem in v:
                          subval = subitem(ctx) if callable(subitem) else subitem
                          if subval:
                              subval = subval if isinstance(subval, list) else [subval]
                              computed_unique.extend([(k, s) for s in subval])
  
              objid = materialize_entity(ctx, _typ, unique=computed_unique)
              objids.append(objid)
              for curr_rel in rels:
                  #e.g. scenario if passed in rel=ifexists(...)
                  curr_rel = curr_rel(ctx) if callable(curr_rel) else curr_rel
                  #FIXME: Fix this properly, by slugifying & making sure slugify handles all numeric case (prepend '_')
                  curr_rel = '_' + curr_rel if curr_rel.isdigit() else curr_rel
                  if curr_rel:
                      if inverse:
                          ctx.output_model.add(I(objid), I(iri.absolutize(curr_rel, ctx.base)), I(o), {})
                      else:
                          ctx.output_model.add(I(o), I(iri.absolutize(curr_rel, ctx.base)), I(objid), {})
              #print((objid, ctx_.existing_ids))
              if objid not in ctx.existing_ids:
                  if _typ: ctx.output_model.add(I(objid), VTYPE_REL, I(iri.absolutize(_typ, ctx.base)), {})
                  #FIXME: Should we be using Python Nones to mark blanks, or should Versa define some sort of null resource?
                  #XXX: Note, links are only processed on new objects! This needs some thought
                  for k, v in links:
                      new_current_link = (I(objid), k, ctx.current_link[TARGET], ctx.current_link[ATTRIBUTES])
                      ctx_vein = ctx_stem.copy(current_link=new_current_link)
                      k = k(ctx_vein) if callable(k) else k
                      #If k is a list of contexts use it to dynamically execute functions
                      if isinstance(k, list):
                          if k and isinstance(k[0], context):
                              for newctx in k:
                                  #The function in question will generate any needed links in the output model
                                  v(newctx)
                              continue
  
                      #import traceback; traceback.print_stack() #For looking up the call stack e.g. to debug nested materialize
                      #Check that the links key is not None, which is a signal not to
                      #generate the item. For example if the key is an ifexists and the
                      #test expression result is False, it will come back as None,
                      #and we don't want to run the v function
                      if k:
                          v = v(ctx_vein) if callable(v) else v
  
                          #If k or v come from pipeline functions as None it signals to skip generating anything else for this link item
                          if v is not None:
                              v = v(ctx_vein) if callable(v) else v
                              #FIXME: Fix properly, by slugifying & making sure slugify handles all-numeric case
                              if k.isdigit(): k = '_' + k
                              if isinstance(v, list):
                                  for valitems in v:
                                      if valitems:
                                          ctx.output_model.add(I(objid), I(iri.absolutize(k, ctx_vein.base)), valitems, {})
                              else:
                                  ctx.output_model.add(I(objid), I(iri.absolutize(k, ctx_vein.base)), v, {})
                  ctx.existing_ids.add(objid)
          return objids
  
      return _materialize