Defining \zip
in the first way is quite easy:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\zip}{mm +m}
{
\seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #1 }
\seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #2 }
\cs_set:Nn \__iagolito_zip:nn { #3 }
\seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq \__iagolito_zip:nn
}
\seq_new:N \l__iagolito_zip_a_seq
\seq_new:N \l__iagolito_zip_b_seq
\ExplSyntaxOff
\begin{document}
\zip{a,b,c}{1,2,3}{#1-#2 }
\end{document}
You can check this prints
a-1 b-2 c-3
If the two lists have different number of elements, the loop ends when either list ends.
The more complex features can be accomplished as well. Beware that the two optional argument must both appear, if the complex processing is needed.
The idea is to populate another sequence where the two lists are merged and then an auxiliary macro can be applied.
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\zip}{oomm +m}
{
\IfNoValueTF { #1 }
{ \iagolito_zip_simple:nnn { #3 } { #4 } { #5 } }
{ \iagolito_zip_full:nnnnn { #1 } { #2 } { #3 } { #4 } { #5 } }
}
\seq_new:N \l__iagolito_zip_a_seq
\seq_new:N \l__iagolito_zip_b_seq
\seq_new:N \l__iagolito_zip_c_seq
\cs_new_protected:Nn \iagolito_zip_simple:nnn
{
\seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #1 }
\seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #2 }
\cs_set:Nn \__iagolito_zip:nn { #3 }
\seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq \__iagolito_zip:nn
}
\cs_new_protected:Nn \iagolito_zip_full:nnnnn
{
\seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #3 }
\seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #4 }
\seq_clear:N \l__iagolito_zip_c_seq
\cs_set:Npn \__iagolito_zip_process:w #1 \q_stop #2 \q_stop { #5 }
\seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq \__iagolito_merge:nn
\seq_map_inline:Nn \l__iagolito_zip_c_seq { ##1 }
}
\cs_new_protected:Nn \__iagolito_merge:nn
{
\seq_put_right:Nn \l__iagolito_zip_c_seq { \__iagolito_zip_process:w #1 \q_stop #2 \q_stop }
}
\ExplSyntaxOff
\begin{document}
\zip{a,b,c}{1,2,3}{#1-#2 }
\zip[#1/#2][#3::#4]
{a/A,b/B,c/C}
{1::I,2::II,3::III}
{Grand #1 is #2 but grand #3 is #4.\par}
\end{document}

If you want to pass macros expanding to lists, do one step expansion.
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\zip}{oomm +m}
{
\IfNoValueTF { #1 }
{ \iagolito_zip_simple:oon { #3 } { #4 } { #5 } }
{ \iagolito_zip_full:nnoon { #1 } { #2 } { #3 } { #4 } { #5 } }
}
\seq_new:N \l__iagolito_zip_a_seq
\seq_new:N \l__iagolito_zip_b_seq
\seq_new:N \l__iagolito_zip_c_seq
\cs_new_protected:Nn \iagolito_zip_simple:nnn
{
\seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #1 }
\seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #2 }
\cs_set:Nn \__iagolito_zip:nn { #3 }
\seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq \__iagolito_zip:nn
}
\cs_generate_variant:Nn \iagolito_zip_simple:nnn { oo }
\cs_new_protected:Nn \iagolito_zip_full:nnnnn
{
\seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #3 }
\seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #4 }
\seq_clear:N \l__iagolito_zip_c_seq
\cs_set:Npn \__iagolito_zip_process:w #1 \q_stop #2 \q_stop { #5 }
\seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq \__iagolito_merge:nn
\seq_map_inline:Nn \l__iagolito_zip_c_seq { ##1 }
}
\cs_generate_variant:Nn \iagolito_zip_full:nnnnn { nnoo }
\cs_new_protected:Nn \__iagolito_merge:nn
{
\seq_put_right:Nn \l__iagolito_zip_c_seq { \__iagolito_zip_process:w #1 \q_stop #2 \q_stop }
}
\ExplSyntaxOff
\newcommand{\listA}{a,b,c}
\newcommand{\listB}{1,2,3}
\newcommand{\listC}{a/A,b/B,c/C}
\newcommand{\listD}{1::I,2::II,3::III}
\begin{document}
\zip{a,b,c}{1,2,3}{#1-#2 }
\zip{\listA}{1,2,3}{#1-#2 }
\zip{a,b,c}{\listB}{#1-#2 }
\zip{\listA}{\listB}{#1-#2 }
\zip[#1/#2][#3::#4]
{a/A,b/B,c/C}
{1::I,2::II,3::III}
{Grand #1 is #2 but grand #3 is #4.\par}
\zip[#1/#2][#3::#4]
{\listC}
{1::I,2::II,3::III}
{Grand #1 is #2 but grand #3 is #4.\par}
\zip[#1/#2][#3::#4]
{a/A,b/B,c/C}
{\listD}
{Grand #1 is #2 but grand #3 is #4.\par}
\zip[#1/#2][#3::#4]
{\listC}
{\listD}
{Grand #1 is #2 but grand #3 is #4.\par}
\end{document}
The output is the same as before, just repeated four times for each instance.