docs: def foreach(origin=None, rel=None, target=None, attributes=None):
      '''
      Action function generator to compute a combination of links
  
      :return: Versa action function to do the actual work
      '''
      def _foreach(ctx):
          '''
          Versa action function utility to compute a list of values from a list of expressions
  
          :param ctx: Versa context used in processing (e.g. includes the prototype link)
          '''
          _origin = origin(ctx) if callable(origin) else origin
          _rel = rel(ctx) if callable(rel) else rel
          _target = target(ctx) if callable(target) else target
          _attributes = attributes(ctx) if callable(attributes) else attributes
          (o, r, t, a) = ctx.current_link
          o = [o] if _origin is None else (_origin if isinstance(_origin, list) else [_origin])
          r = [r] if _rel is None else (_rel if isinstance(_rel, list) else [_rel])
          t = [t] if _target is None else (_target if isinstance(_target, list) else [_target])
          #a = [a] if _attributes is None else _attributes
          a = [a] if _attributes is None else (_attributes if isinstance(_attributes, list) else [_attributes])
          #print([(curr_o, curr_r, curr_t, curr_a) for (curr_o, curr_r, curr_t, curr_a)
          #            in product(o, r, t, a)])
          return [ ctx.copy(current_link=(curr_o, curr_r, curr_t, curr_a))
                      for (curr_o, curr_r, curr_t, curr_a)
                      in itertools.product(o, r, t, a) ]
          #for (curr_o, curr_r, curr_t, curr_a) in product(origin or [o], rel or [r], target or [t], attributes or [a]):
          #    newctx = ctx.copy(current_link=(curr_o, curr_r, curr_t, curr_a))
              #ctx.output_model.add(I(objid), VTYPE_REL, I(iri.absolutize(_typ, ctx.base)), {})
      return _foreach