Simple functions in Vim are declared like this:
function! A(a, b, c)
echo a:a a:b a:c
endfunction
call A(1, 2, 3)
|
There’s probably nothing surprising there except for the
a:a
syntax, which is how Vim insists on accessing the function’s arguments
(mnemonic: a: for argument).
Just as simple is calling function
A() from another function,
B(),
passing its arguments directly along to
A():
function! B(a, b, c)
return A(a:a, a:b, a:c)
endfunction
call B(1, 2, 3)
|
Nothing surprising there at all. But we’ve just laid the groundwork
for the main attraction tonight. In VimL, you can call a function
using the library function
call(func, arglist) where
arglist
is a list. If you’re calling a function that takes multiple arguments,
collect them in an actual list like this:
function! C(a, b, c)
return call("A", [a:a, a:b, a:c])
endfunction
call C(1, 2, 3)
|
If you already have the elements in a list, no need to wrap it in an
explicit list:
function! D(a)
return call("A", a:a)
endfunction
call D([1, 2, 3])
|
Let’s step it up a notch. What if you want to be able to accept the
args as either separate arguments or as a list? Vim has your back with
variadic functions cloaked in a syntax similar to C’s:
…
Variadics in the key of V:
-
a:0 is a count of the variadic arguments
-
a:000 is all of the variadic arguments in a single list
-
a:1 to a:20 are positional accessors to the variadic
arguments
|
So now it doesn’t matter how we receive the arguments — standalone or
in a list — we can keep Vim happy and call
A() appropriately.
function! E(...)
if a:0 == 1
return call("A", a:1)
else
return call("A", a:000)
endif
endfunction
call E(1, 2, 3)
call E([1, 2, 3])
|
Ok. That’s not too bad; it’s perhaps a little awkward. We’re calling
A() directly here, but it shouldn’t be a surprise to see that we
can call
C() in the same way too:
function! F(...)
if a:0 == 1
return call("C", a:1)
else
return call("C", a:000)
endif
endfunction
call F(1, 2, 3)
call F([1, 2, 3])
|
Pretty straightforward. What about calling
D() instead which
expects a single list argument? Hmm… if Vim wants a list, give him
a list:
function! G(...)
if a:0 == 1
return call("D", [a:1])
else
return call("D", [a:000])
endif
endfunction
call G(1, 2, 3)
call G([1, 2, 3])
|
It’s worth stopping briefly here to consider what
call() is doing
to that arglist: It’s
splatting it (extracting the arguments and
passing them as separate members to the called function). Nice.
Wouldn’t it be nice if we could splat lists ourselves? Well, be
envious of Ruby coders no more because we
can splat lists in VimL!
Splat!
To splat a list into separate variables (a, b and c here):
let [a, b, c] = somelist
Read :help :let-unpack for the juicy extras. |
I like the splatting approach because it gives us variable names to
play with inside our function:
function! H(...)
if a:0 == 1
let [a, b, c] = a:1
else
let [a, b, c] = a:000
endif
return D([a, b, c])
endfunction
call H(1, 2, 3)
call H([1, 2, 3])
|
Of course, it works just as well for calling functions with explicit
multiple arguments, like
C():
function! I(...)
if a:0 == 1
let [a, b, c] = a:1
else
let [a, b, c] = a:000
endif
return C(a, b, c)
endfunction
call I(1, 2, 3)
call I([1, 2, 3])
|
You’ll notice that the splat semantics are identical between
H()
and
I() and only the call of
D() and
C() change,
respectively. This is very neat, I think.
So far we’ve been calling through to functions that call
A()
directly. Happily, we can call through to one of these dynamic
functions (like
E(), but any would work as well) and have it Just
Work too:
function! J(...)
if a:0 == 1
let [a, b, c] = a:1
else
let [a, b, c] = a:000
endif
return E(a, b, c)
endfunction
call J(1, 2, 3)
call J([1, 2, 3])
|
So, that’s it. Vim has variadic functions and splats. And splats are my
recommended pattern for handling deep call chains between variadic
functions.
There’s one last, cute, little thing about splats: you can collect a
certain number of explicit arguments as you require, and then have any
remaining arguments dumped into a list for you. The
rest variable
here will be a list containing
[4, 5, 6] from the subsequent
calls:
function! K(...)
if a:0 == 1
let [a, b, c; rest] = a:1
else
let [a, b, c; rest] = a:000
endif
echo "rest: " . string(rest)
return E(a, b, c)
endfunction
call K([1, 2, 3, 4, 5, 6])
call K(1, 2, 3, 4, 5, 6)
|
And I thought this was going to be a short post when I started. I
almost didn’t bother posting it because of that reason.
Thanks Barry, I didn't know Vim-L was so powerful to support variadic functions and list unpacking.
ReplyDeleteIs there a quick and dirty way to test and practice Vim-L? For example, in Python, I just have to go to python prompt and then I can try every feature that language has to offer.
I understand, we don't have a Vim-L prompt in the terminal, but can I setup my Vim to quickly edit-execute Vim-L snippets to learn and test the language? Can you please point me to the right direction?
A fairly typical development process is to save your work as you go and use:
ReplyDelete:so %
whenever you want to execute the buffer (file). You could map the save+execute steps to a key to speed this up:
nnoremap x :w:so%
D'Oh. the interwebs ate my angle brackets :-(
ReplyDeleteThat was meant to be a leader map.