Monday, January 13, 2014

Anonymous Functions in VimL

Admit it, you’ve wanted this for a long time now.

Update: These two functions are now available in VimaholicsAnonymous.

VimL’s list sort() method allows the caller to provide a custom comparison function with which to sort by. Unfortunately, VimL doesn’t support anonymous functions (sometimes called lambdas), so the caller is forced to pre-write their comparator as a full-fledged function and provide its funcref to sort(). Well, that ends today. Now, VimL has anonymous function love! Check it:

This humble little snippet of code lets vimmers declare anonymous functions on the fly:


let fn_idx = 0
function! Fn(fn_form)
  let [fn_args, fn_body] = split(a:fn_form, '\s*=>\s*')
  let g:fn_idx = g:fn_idx + 1
  let fname = 'AnonFn_' . g:fn_idx
  let b_elems = split(fn_body, '|')
  let b_elems[-1] = 'return ' . b_elems[-1]
  exe 'func! ' . fname . fn_args . "\n"
        \. join(b_elems, "\n") . "\n"
        \. 'endfunc'
  return function(fname)
endfunction

Like this:


let x = ["one","two","three","four","five","six","seven","eight","nine","ten"]
echo sort(x, Fn('(a, b) => len(a:a) > len(a:b)'))

The magic is in the Fn() call. It takes a string argument of the form:
(arguments) => function-body statements separated by | (pipe)

The last statement will be implicitly returned by the anonymous function, so no need to explicitly add a return statement.

Unicorns, or what?!

What? You want more. Certainly, sir. Behold:


function! Fx(fn, ...)
  return call(a:fn, a:000)
endfunction

That lets you execute an anonymous function, like:


echo '2 ^ 6 = ' . string(Fx(Fn('(a, b) => pow(a:a, a:b)'), 2, 6))

Any cooler and you’d need your jumper! :-D Oh, but there’s more… I’m just getting warmed up!

Just because I like you, here’s a little extra gift:


let Mul = Fn('(a,b)=>let x = a:a | let y=a:b | x*y')
echo '5 * 6 = ' . Fx(Mul, 5, 6)

That lets you call your funcref'd anonymous function by name. Did you see what I did there? Yes, I cheated God! I named an annonymous function. Cool, eh?

So, apart from the obvious, is this really that amazing? My vote is: YES! I have plans for this technology. Here’s a sneaky hint of where I’m looking to take this:


CompileMacros

Mul = ((a,b) => let x = a:a | let y=a:b | x*y)

echo '5 * 6 = ' . #(Mul, 5, 6)
echo '2 * 6 = ' . #(((a, b) => a:a * a:b), 2, 6)

That’s right. I want hygenic macros in VimL. And that code works in my current experiments. The notation is stolen from… clojure I believe, where #(…) executes a lambda.

The call to CompileMacros will process any macros in the surrounding expressions before sourcing the result. Just for completeness, defining macros currently looks like this:


call Macro('(\+\((.\{-})\s*=>.\{-}\))', "Fn('\\1')")

call Macro('\%(\n\|^\)\@<=\s*\(\w\+\)\s*=\s*\(Fn(.*=>.\{-})\)\s*\n',
      \ "let \\1 = \\2\n" )

call Macro('#(\(.*\))\n', "Fx(\\1)\n")

call Macro('#Fn(\(.\{-}\))\n', "Fx(Fn(\\1)\n")

But that will change in the next version. Using regex to parse is an abomination; I was merely proof-of-concepting here. I’ll use VimPEG instead.

Stay tuned, if bending the spoon is what you’re in to.

Sunday, January 12, 2014

Help Us Help You

I know: you want to be a good #vim citizen; you want to be a good vimmer; you want to learn and grow and solve your problems in a timely manner and do so in a safe and supportive environment filled with smart, caring and funny peers.

I know; we all do. And we all can. Here’s how:

Help Yourself

Vim has an awesome built-in manual filled with more answers than you have questions. Almost every problem you have is addressed somewhere within its many chapters. Unfortunately, newcomers often can’t find what they’re looking for in the manual, or don’t understand the answer because of a lack of jargon or fundamental Vim-specific knowledge. This feeling of inaccessibility passes over time as you level up in Vim, but can be debilitating to the beginner. But despair not! When you don’t understand something in the manual, then that is the time for asking on #vim, but more on that later. First, here are some tips for helping yourself to some Vim love:

Note I use the loose term manual here to treat the reference-manual and user-guide as a whole, just as the built in tools for navigating them do.
  • :help topic will jump to topic within the manual.
  • :helpgrep pattern will search for all occurrences of pattern throughout the manual. Use :cope to open the quickfix window of search results.
  • There is a decent FAQ: http://vimhelp.appspot.com/vim_faq.txt.html
  • The #vim channel has a fact bot called vimgor filled with answers to common problems. These facts are often keyed on weird terms that must be supplied to vimgor precisely to elicit their associated gems of wisdom, which is practically useless to the uninitiated. Thankfully, vimgor supports a listfacts interface that will return all known facts containing the given term, e.g:
    /msg vimgor listfacts file
    will list all known keys containing file.
Note You should explore vimgor using either /msg or /query to limit interference on the #vim channel.
Feel free to call upon vimgor directly from within #vim if you are using it to answer someone else’s question. Here are some additional tools in that vein:
  • ;help topic ⇐ will provide a URL to Vim’s manual for the given topic.
  • ;man topic ⇐ will provide a URL to the appropriate unix man page.

Ask For Help

So you’ve looked in the manual and can’t find an answer, or don’t understand the answer; you’ve looked at the FAQs and checked in with vimgor to no avail. Now is the time to ask for help on #vim. Alternatively, ask reddit's vim channel. The guidelines for asking on #vim apply equally well for reddit's forum, of course.

Both for reasons of courtesy to others and self-edification, it’s important to try to find the answers to your own questions yourself before asking others on #vim. But just as important is how you ask:

For simple, easy and short examples, asking inline is acceptable and preferred, but if your problem is dense or requires more than a couple lines of data, using a pastebin service is preferred. Github’s gist is popular, but any decent ad-free pastebin will be happily tolerated. The format of your pastebin is also important. Here is an excellent example: https://gist.github.com/2759774

Etiquette

It’s obvious to most people that asking politely will yield better responses; some people need to be told explicitly. The good folk of #vim freely donate their time and energy to helping others discover the joy of being a vimmer. They delight in sharing their arcane knowledge with interested learners and inquisitive minds. In case you’re having trouble reading between the lines here, don’t approach #vim with these attitudes:
  • impatience or unwillingness to learn: You don’t care to read the :help topic provided, you just want the solution to your exact problem RIGHT NOW. Nothing upsets a vimmer more. The :help is extremely well written if not a bit obtuse for newcomers, but we’ll help you understand those intricacies — very rarely is someone told to rtfm and then left to drown in their own incomprehension.
  • rudeness or ungratefulness: diagnosing your problem might take some time and several steps requiring your participation. If you get frustrated at this or interact poorly then your chances of continued support diminish quickly. Be nice, get nice; say please and thank you.
One of my greatest teaching mentors gave me this gem once:
Teach as if your mentor is watching from the corner.
The analogy here is: ask as if you weren’t sitting behind a terminal; ask as if your mum or boss or teacher or doctor or someone you respect were listening.

In fairness, I have rarely seen such bad behaviour on #vim; it’s generally one of the most polite chat rooms I’ve ever seen. Let’s keep it that way! :-)