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

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