Monday, June 9, 2014

Tag Like a Boss

tagging the way god intended


This short article aims to get you working with Vim tags quickly and effectively, using its built-in support. Your heavy plugins can wait outside.

Before we can jump in and learn the cool commands and power moves, we have to take charge of our environment.

Preparing for a Tagged Lifestyle


You will need:
  • A ctags generated tags file (Exuberant Ctags is the choice for most cases)

    Typically this is as easy as:

    ctags -R

    in the root of your project.
    But if you need anything fancier than that, consult  man ctags  for guidance.
  • A correctly set :help 'tags' option

    The Vim default of ./tags,tags is probably sufficient for most projects but you might want to include library tag files or a project-common tags file.

  • A correctly set :help 'path' option

    My preferred default is:

    set path=.,**

    Which searches the directory of the current file and all directories beneath the current directory. See :help file-searching for more details.

These options can be set in your $MYVIMRC or, better, within filetype specific plugins in ~/.vim/ftplugin/<the-filetype>.vim or ~/.vim/after/ftplugin/<the-filetype>.vim

With the right setup, we can now enjoy a happy tagging lifestyle.

Living with Tag Love

There are many tag commands available in Vim, but I’m going to share with you only a select few — a mere dozen or so. These are the ones I most frequently reach for. You can learn the other tag commands later.

ctrl-]
Jump to the keyword under the cursor. Tag jumps are recorded on a :help tag stack.

ctrl-t
Jump to older tag in the stack.

:ta
Jump to newer tag in the stack. :help :tag

:0ta or :0tn
Jump to previously jumped-to tag. I use this one often after wandering away from the place I tag-jumped into the current file.

:ts /something
Show a list of tags matching the pattern something.

TIP: Use <ctrl-d> to show a list of tag candidates. This works with partial matches too.
Read more with :help c_CTRL-D

g]
Show a list of tags matching the keyword under the cursor.

:tj /something
Show a list of tags matching the pattern something. If there is only one tag in the list, don’t show the list but instead jump directly to it.

g ctrl-]
Show a list of tags matching the keyword under the cursor. If there is only one tag in the list, don’t show the list but instead jump directly to it.

[I
Ordinarily, this just shows all lines in the file matching the keyword under the cursor — a shortcut to :g/<c-r><c-w>

This map (taken from the Vim help) lets you jump to one of the matches:

:map <F4> [I:let nr = input("Which one: ")<Bar>exe "normal " . nr ."[\t"<CR>

Note
  • Use ctrl-c to cancel the choice if you don’t want to jump to any of them.
  • Use `` to jump back to where you were if you accidentally pressed <esc> or <enter> instead.

Courteous Cousins

While not strictly tagging commands, these little gems are semantically related:

gd and gD
Jump to the local or global declaration of the keyword under the cursor, respectively.

gf
Jump to the file under the cursor.

Honourable Mentions

While these are not in my daily tag toolbox, I do call upon them occasionally:
:tags
To see my current tag stack.

:ptj /something
To show the tag match in the :help preview-window.

ctrl-w ctrl-i and ctrl-w ctrl-d
To split the window, showing the associated first line or definition, respectively.

Tag! You’re It!

Using tags within Vim will speed up your editing by making it easy for you to jump around your pile of files. While there are heavy plugins that aim to make this prettier, the seasoned vimmer knows that the extra bling doesn’t add any real value to their edits.

Vanilla, when done right, is a classy choice.

Get yourself setup to use tags correctly within your projects and get on living the tag lifestyle!

Wednesday, May 28, 2014

For Argument Sake

The buffer list is dead! Long live the buffer list!


The Buffer List

I have long been a fan of :ls and the accompanying :b partial match on buffer name. That basic functionality is rock solid. I still recommend it to newcomers to our beloved editor. We’ve built plugins around it.

There is only one problem with the buffer list. Well, okay, two problems:
  1. It isn’t able to be reordered,
  2. It isn’t able to be renumbered.


Ordering

I sometimes wish I could re-order the buffers so that I could group related ones together: all my text files together and all my source files before them, say. The buffer number is fixed at the time the buffer is created and can never be changed throughout the lifetime of the Vim session - it’d be much nicer to be able to re-order these as and when you saw fit. You can’t do this with the buffer list.


Numbering

When buffers are removed from the buffer list, they leave holes in the numbering of listed buffers. Many plugins use temporary buffers (some a LOT) which can leave huge holes in the numbering of your buffers. If you like to jump to buffers by remembering their buffer number, it can be a bit unsettling to know that you need to jump to buffers 1, 2, 6 and 11 - it’d be much nicer to (even if only temporarily) renumber those buffers to: 1, 2, 3, and 4 respectively. You can’t do this with the buffer list.

But not all is lost. We have an alternative in Vim. We have the argument list!


Argument List

Read more about this little gem with :help arglist
Each window can have a separate argument list. You’re free to set and reset the argument list as and when you see fit. Let’s take a short walk down argument lane:

You can slurp up the .c files in the src/ directory into your current window’s argument list:

:args src/*.c

You can do that recursively within all of the subdirectories:

: args src/**/*.c

You can see your current argument list with:

:args

You can add the .h files to the argument list with:

:argadd src/**/*.h

You can jump to an argument by partial buffer (file) name match with:

:argedit {partial name}

You can jump to an argument by (1-based) index with:

:argument {index}

You can perform an operation on all of your arguments in a single command:

:argdo %s/Long live \zsthe buffer list\ze!/argument lists/ge | update

Early Quitters:

If you :quit before visiting all of the files in your argument list, Vim will question your intentions. To prevent that, use :qa instead.

Local vs Global:

If you spawn a new window, it will inherit the parent window’s argument list. There are functions which will attach to the global argument list or create a new local argument list for the current window.

:help argglobal
:help arglocal

Awesome, right?

Could it get any better? Do I ever ask that without candy in my pocket? Make an appointment with your dentist because this is so sweet, it’ll rot your teeth:


VimFindsMe

The light-weight file finder, VimFindsMe (VFM) now supports the argument list. By default, the <leader>ga map will open the current argument list into a scratch buffer. You can add and remove files and reorganise them as you see fit. You can see their positional index by enabling the :setlocal number option. When you press <enter>+ in this scratch buffer, VFM will set your argument list to these files, in this order.


From :VFMEdit

The :VFMEdit command (mapped to <leader>ge by default) lets you filter a find result on your :help path option. If you press <enter> on a file from this window, it will be opened as a new buffer (but not added to your argument list). If you’d like to set your argument list to the files in the VFM scratch window, type:

:VFMArgs

If instead you’d like to add all of the files to your argument list, type within the VFM scratch window:

:VFMArgadd

The cumbersome combination of :argument and :argedit have been combined into one convenient function called VFMArgument(arg) where arg can be either an argument index or partial buffer name. Remember that the argument index is not the buffer number. You can see the list of arguments with either the :VFMArgs command or the builtin :args command. The buffer name given must exist in the current argument list. This function has a corresponding command:

:VFMArgument {arg}

Which is triggered by <plug>vfm_argument (mapped to <leader>gg by default). The command supports argument list buffer name completion.


You Can’t Do This With The Buffer List

The list of :buffers (aka :ls aka :files) is still useful, don’t get me wrong. It is an unwavering record of buffer numbers and names, unassailable throughout the lifetime of your Vim session. Ignoring :bdelete and :bwipe for a minute. But apart from that, completely inviolable. Excepting certain &buftype settings that make buffers unlisted, of course. Besides all that… watertight.

The argument list on the other hand is an ephemeral construct, changeable on a whim and created with ease. It allows you to arbitrarily order and reorder your buffers as often as you need. And you can perform a string of commands against each buffer in your argument list with ease.

The argument list has a few warts, though, like having two commands for switching to an element by index or by name, and a cumbersome method of deleting arguments. VimFindsMe solves these problems by providing a better interface for setting, modifying and deleting the argument list (:VFMArgs), and a single integrated command (:VFMArgument) for jumping to an argument by index or name.

Care to argue?

Sunday, April 6, 2014

Seconds Count, Not Keystrokes


It’s not how short it is that matters, it’s how quick you are.
at the keys, not under the sheets
— Vimmers

Golf: A good edit ruined.

The Problems:
  • Vim Golf optimises on the wrong thing: keystrokes
  • Vim Golf demands a bare-metal Vim

Least Number of Keystrokes

Banging out thirty characters in a few seconds to solve a problem is immeasurably better than spending several minutes contemplating the fewest keystrokes possible to achieve the same result. The point here is, time is a better metric to test yourself against. Can you solve this problem any faster than the last time you tried? Perhaps since then, you’ve crafted a new map or text-object or macro to simplify the task. Isn’t that exactly what Vimmers should be doing? It is Bram’s key point in Seven Habits of Effective Text Editing suggestions, after all.

Bare-Metal Vim

EVERYONE uses a more able Vim than what Vim Golf insists upon. Everyone has better features enabled, Useful Plugins, and custom commands, maps and abbreviations to lighten their editing load. I get that competition requires a level playing field, but that in turn is hindering the competitors. Vimmers walk away from their morning on the golf course with less than they should. They should be able to utilise everything they practised.


Playa:  Aw… but how can I show my l33t skilz? :-(
Vimmer:  Play Vim Golf


Is There No Place for Vim Golf?

Of course there is. It has its uses. If you like playing, keep at it. I’ve seen positive effects in some Vimmers. I implied earlier that "spending minutes contemplating a fewer keystroke alternative" was inefficient and that you’d be better served by sticking to a more verbose alternative that you know well. To be clear, what I mean here is that if what you craft on the golf course is arcane, complex and immemorable (albeit awesomely short), you will end up not using it in your daily editing. Sure, you could try to make it a habit, but then you run into problem #2 — don’t remember a cryptic collage of keystrokes when Vim can do it for you, in a map, say.

Learning shorter ways to get stuff done in Vim is fun and enlightening. I know. I’ve spent many hours on this journey too. But it is a journey; the benefit comes from the process, not the product. Learning that there is a keystroke that lets you type Normal Mode commands in Insert Mode is eye-opening and, at first, spawns a desire to play with this shiny new toy. The Learned Vimmer soon realises though that this shiny new bauble is better left in the box and only taken out when maps are being made.
  • The process: discovery, assessment & assimilation.
  • The product: a new command that you really shouldn’t use when live-typing.

You want to learn every feature the editor offers and use the most efficient command all the time.
You will waste a lot of time learning things you will never use.

Seven Habits of Effective Text Editing - How not to edit effectively
— Bram Moolenaar


Perfect Practice Makes Perfect

Practice doesn’t make perfect. Perfect practice makes perfect.
Charles Birch
— Taekwondo Master
I learnt that quote first-hand from the inimitable Mr Birch. I internalised it at the time, reflect on it frequently, and counsel my own students likewise.

The point of the quote is: If you practice the wrong thing, then you’ll learn well to do it wrong. An additional note here is that repairing a bad habit costs more than learning good habits to begin with. The apocryphal metric tossed about on the training floor was: it takes 50 repetitions to learn a new move, and 200 to unlearn a bad one. Regardless of the accuracy of those particular numbers, the phenomenon it describes is nonetheless very real.


A Better Way

Use Your Vim, Luke

Vim Golf requires that you use a crippled Vim. No. Use your Vim. You’ve grown your ~/.vimrc with its suite of maps, abbreviations, commands and functions designed around your Vimming habits, and you’ve installed plugins that further enhance your editing prowess. Use them! Practice them. Cultivate them: throw away bad ones; repair broken ones; develop new ones.

Compete Against Younger You

Instead of comparing your keystroke length to that of others, you’re better off competing against your own Personal Best times for the editing task at hand.

Method

  1. Grab an editing task. These can come from your own experience or you can just use those on Vim Golf.
  2. Reflect on your prior attempt of this problem (you are repeating your practice exercises, right?!)
  3. Grab your stopwatch; time your execution; record the results.
  4. Reflect again. How did you go? Did you improve your Personal Best time? If not, why not? What are the time-sinks? What can you improve? Would a map help? Or a macro? A plugin?

Macros, Such Fleeting Pleasures
Have you ever crafted an awesome macro to refactor code, or munge data into structures, or perform a tedious operation that frequently arises in this particular file, task or programming language, only to lose all that effort when you close Vim? Sure, with :help 'viminfo you can have Vim persist your registers (in which your macros are stored), but their fragile nature means that you or any one of your plugins can overwrite them without warning.
VimLocalMacros was written to solve this problem. It stores the contents of registers inside comments within the current file so that when you come back to the file, you can restore the registers and have your macros at the ready without having to remember and recreate them over and again.

Those four steps sound easy enough, right? It couldn’t get any easier! Er, well, this is Vim. Of course it could be easier! :-) Get to the VimGym and let it handle all that finicky stuff, like:
  • keeping an extendible store of practice exercises
  • timing your attempts and storing the results
  • analysing your game, suggesting improvements and forcing extra practice in problem areas

VimKata was one of the very first Vim plugins I ever worked on, and so it was therefore doomed to failure from the outset. I didn’t have the skills necessary to tackle such a beast. I didn’t know how to build it; I didn’t know what to focus on; I didn’t know what I didn’t know.
VimGym is less than VimKata in some ways and therefore so very much more.

So, in summary, my thoughts on Vim Golf and Deliberate Practice for Vim are:
  • Vim Golf hobbles Vimmers by limiting them to an unrealistically featureless Vim
  • Vim Golf measures the wrong thing: time taken to complete the task is more important than the number of keystrokes
  • Improvement in a skill comes from repeatedly attempting tasks that are at the edge of your abilities and getting feedback on performance. Ideally this would come from an expert, but VimGym uses the task completion time as an acceptable fallback.
  • Vimmers should be adopting a Seven Habits to Effective Text Editing approach to Vimming whereby they identify inefficiencies, craft improvements, and exercise them to make them habitual.

Whether you choose to go it alone, stick with just VimGolf, or try VimGym too, I wish you well on your journey.

Saturday, March 22, 2014

Closures in VimL


I’ve talked about Anonymous Functions in VimL before, but what about closures?

What is necessary for a language to be able to say that it supports closures? What is sufficient for someone to be able to fake their way through it?

Here is the first example from Wikipedia’s Closure article:


def start(x):
    def increment(y):
        return x+y
    return increment

first_inc = start(0)
second_inc = start(8)

first_inc(3)   # returns 3
second_inc(3)  # returns 11

# The x value remains the same for new calls to the function:
first_inc(1)   # returns 1
second_inc(2)  # returns 10

Here is a faked-up alternative in VimL:


function! Start(x)
  let obj = {}
  let obj.x = a:x
  func obj.increment(y)
    return self.x + a:y
  endfunc
  return obj
endfunction

let first_inc = Start(0)
let second_inc = Start(8)

echo first_inc.increment(3)
echo second_inc.increment(3)

echo first_inc.increment(1)
echo second_inc.increment(2)

Okay, so not really what you think of as a closure, but it lets you get similar things done. Wikipedia says:
Closures are typically implemented with a special data structure that contains a pointer to the function code, plus a representation of the function’s lexical environment (i.e., the set of available variables) at the time when the closure was created.
— Wikipedia
The manual marshalling in our object-oriented implementation above probably doesn’t count, but it’s a similar idea… isn’t it?

Vim certainly has First Class Functions and with VimaholicsAnonymous, it has Anonymous Functions… Does it have closures? If not… does it matter? Can you achieve something similar using its prototypal object notation?

Sunday, March 2, 2014

Welcome to the Search Party

Enhanced searches, highlights and matches for Vim

Raimondi and I wrote a nifty little plugin and, well, we’d like to invite you to join the SearchParty.

SearchParty has these awesome features:

Visual Searches

  • * Searches for the next occurrence of the currently selected visual text.
  • # Searches for the prior occurrence of the currently selected visual text.
  • & Starts a :substitute using the currently selected visual text.
I’ve had a goodly amount of <3 on #vim for the visual & command.

Literal Search

<leader>/ prompts for a literal string to search for. This does NOT use a regular expression, so the characters you type here will be searched for literally without any magic interpretation. The <Up> key scrolls through the prior literal search history.

This one is a real crowd pleaser. People come for the highlight candy, but they stay for Literal Search.

Ranged Search


    :7,12 RSearch foo
Searches for "foo" only within the range from lines 7 through 12, both inclusive. The default range is % (the whole buffer).

Tip The normal next and previous keys (n and N) cycle within the range.

Multiple Replacements

<leader>mp prompts for a Search term and then prompts for space separated Replacement terms (use \\ to escape desired spaces). The current line is then duplicated as many times as there are replacements, minus one, and the Search term is then replaced on each line with each successive Replacement.

Perhaps an example would far better explain this:

Given the line:

   Don't credit it on the sunshine

With the cursor on that line, the command:

<leader>mp

Followed by:

Search:credit
Replace:blame

Will produce:

   Don't blame it on the sunshine

That’s no better than :s/// I know, but with the cursor still on the same line, check it:

<leader>mp

Followed by:

Search:sunshine
Replace:moonlight good\ times

Will produce:

   Don't blame it on the sunshine
   Don't blame it on the moonlight
   Don't blame it on the good times

Note Blaming it on the boogie is left as an exercise for the reader.

Search Highlighting

IIRC, this is where SearchParty all began — the ability to easily highlight the word under the cursor without silly machinations like: *#
  • <C-L> Temporarily clears search highlight.
  • <C-BSlash> Toggles search highlighting.
  • <leader>* Highlights all occurrences of word under the cursor.
  • <leader>mah Toggle automatic highlight of all occurrences of word under cursor.
  • <leader>g* Highlights all occurrences of WORD under the cursor.
Note You might think that <leader>mah is a bit verbose but toggling automatic word highlighting is not something I think you’ll need to do very often. If it does bother you, though, this mapping and all of the mappings in SearchParty are <Plug> maps, so you can customise them to your own tastes. Instructions for doing so are in the plugin docs.

Highlighting Print Command

Modern grep commands highlight the search term within the resulting lines (if you so desire). With SearchParty, Vim’s :g// command does too now:

    :g/something/P
Will show the matching lines with all occurrences of "something" on those lines highlighted.

Tip This command can also be used for an arbitrary range and it will highlight the most recent search pattern (@/) within those lines. E.g.:

    :10,20P

Set Search

Sometimes you’d like to highlight that word over there without having to go there, use <leader>* on it, and come back. <leader>ms is the answer.

Matches

This is one of my personal favourites — the ability to have up to six different strings highlighted in big, bold, bright colours all across the screen wherever they appear. The <leader>mm command prompts you for a string (kindly inserting the current word for you) to highlight. If you go past six then the first one is forgotten and replaced with your latest string (cycling like this ad infinitum). I find this useful for ensuring that a few particularly important strings don’t escape my attention throughout a document.

M.A.S.H.

Lastly, we have the Motion Activated Search Highlighter: when you press n/N/#/*/g#/g*, it highlights the match under the cursor differently to all the other matches on screen (if you have :set hlsearch activated). If you don’t use hlsearch, then it will still highlight the current match.

I really like this feature. I used to find it difficult to see where my cursor was when it was within a highlighted search term — the other similarly highlighted blobs would all compete for my attention. M.A.S.H makes this a no-brainer now.

SearchParty

So, welcome to the party. Grab yourself a buffer and get highlighting, make some matches or MASH out on the lounge. Enjoy your evening.

Saturday, February 22, 2014

Y u no, Vim?

Well, Y you can, it would seem…

I was recently reminded (hi, dhruvasagar) of the Y combinator while idling on #vim and I thought to myself… can you Y, Vim? I decided to find out.

I just recently wrote about Anonymous Functions in Vim and they form the cornerstone of my attempt at Y-ing here. I used Mike Vanier’s excellent article: The Y Combinator as a guide (any and all errors are wholly mine); reading it will fill in the huge holes I have chosen to skip over here.

Mr Fibonacci is in the studio with us today, representing la raison d’recurse.

Mr Fibonacci, Zero?
"=> 0
Lovely! Er… and 14?
"=> 377
Amazing!

Now to some code… First up is the standard recursive definition of Fibonacci:
function! RecursiveFibonnaci(n)
  if a:n == 0
    return 0
  elseif a:n == 1
    return 1
  else
    return RecursiveFibonnaci(a:n - 1) + RecursiveFibonnaci(a:n - 2)
  endif
endfunction
And asking our studio guest…
echo RecursiveFibonnaci(10)
"=> 55
Decent. Now just for fun, here’s the same thing written as an Anonymous Function:
let RF = Fn('(n) => if a:n == 0'
        \.'|  return 0'
        \.'|elseif a:n == 1'
        \.'|  return 1'
        \.'|else'
        \.'|  return call(g:RF, [a:n - 1]) + call(g:RF, [a:n - 2])'
        \.'|endif')
Note Fn() is provided by VimaholicsAnonymous.
How does she measure up, Mr Fibonacci?
echo RF(11)
"=> 89
…and does it agree with our recursive stalwart?
echo RecursiveFibonnaci(12) == RF(12)
"=> 1
Standard! But we haven’t even begun to look at Y yet, so here is Mike’s AlmostFibonacci a la Vim:
function! AlmostFibonacci(f)
  silent! unlet b:f
  let b:f = a:f
  return Fn('(n) => if a:n == 0'
        \.'|  return 0'
        \.'|elseif a:n == 1'
        \.'|  return 1'
        \.'|else'
        \.'|  return call(b:f, [a:n - 1]) + call(b:f, [a:n - 2])'
        \.'|endif')
endfunction

You will notice that it contains an almost identical copy of the anonymous recursive Fibonacci (RF) we declared above. It’s not quite the same, of course, because this version is not meant to be self-recursive. It calls the function f provided to AlmostFibonacci. The little silent! unlet b:f dance and the use of a buffer variable in the first place is just some necessary mechanics to pass AlmostFibonacci's f argument into the inner anonymous function. Basically, we cheat through the use of a global. I use a buffer-local here as a baby-global.

How are we doing, Mr F.?
echo call(AlmostFibonacci('RecursiveFibonnaci'), [12])
"=> 144
echo RecursiveFibonnaci(12) == call(AlmostFibonacci('RecursiveFibonnaci'), [12])
"=> 1
Respect.

Heh… The astute among you are half way through a hate comment right now that I have violated one of the laws of thermodynamics, or at least common decency by employing RecursiveFibonnaci in the call to AlmostFibonacci there. Relax. I know I’m cheating. I just wanted to prove that the code worked. We’ll get to actual Y below. :-p

I hesitated whether I would even show this next piece. It’s using the exact same functions as we just used above, but I am using a slightly different call() interface. I actually use this call() interface in the real Y; I wanted you to know that this difference wasn’t.

echo call(call('AlmostFibonacci', ['RecursiveFibonnaci']), [13])
"=> 233
echo RecursiveFibonnaci(13) == call(call('AlmostFibonacci', ['RecursiveFibonnaci']), [13])
"=> 1

And here she is: Y in Vim!
function! Y(f)
  let b:f_ = a:f
  return call(a:f, [Fn('(x) => call(call("Y", [b:f_]), [a:x])')])
endfunction

Again, you will notice the same buffer-local dance for marshalling the function in a:f into the inner anonymous function.

And, hopefully unsurprising by now, here is how we define Fibonacci with Y:
function! Fibonacci(n)
  return call(call('Y', ['AlmostFibonacci']), [a:n])
endfunction

Still kosher, Mr F.?
echo Fibonacci(14)
"=> 377
echo RecursiveFibonnaci(14) == Fibonacci(14)
"=> 1
Splendid! But… how does the Y version compare to the recursive version?
let start = reltime()
call RecursiveFibonnaci(20)
echo reltimestr(reltime(start))
"=> 0.99 seconds
uh huh…
let start = reltime()
call RF(20)
echo reltimestr(reltime(start))
"=> 1.066 seconds
I guess the indirection there causes some overhead, but nothing disastrous…
let start = reltime()
call Fibonacci(20)
echo reltimestr(reltime(start))
"=> 14.56 seconds
:-( There is no God!

It would seem that although "possible", Y in Vim is not advised*. Or, at least, not the way I approached it. Perhaps there’s a brighter way? I’d love to see it.

(*): Y is not advised anywhere, as far as I can tell, except as a mind-stretching exercise from the church of functional dys.

Just to scare little Vimmers late at night, after running this code, the number of anonymous functions in my Vim session was 48098! This code maims unicorns.

That’s Y!

Tuesday, February 11, 2014

What If The Romans Had Vim?


Note This is much more about VimL than MDCLXVI strings; if this code doesn’t rape your cat, you’re welcome.

Call it a kata if you will; I decided over breakfast to have a go at writing a Roman Numeral to Arabic Number converter in VimL. It’d been sufficiently long since I’d written such a converter in any language, and I’d never done one in VimL, so I thought, why not?

I prefer to think away from the console, so I grabbed a notepad and pencil and started doodling a solution based on how I think about parsing Roman Numerals in wet-ware. Roughly, that looked a bit like:

to_arabic(str)
  new stack
  for each roman_letter in str
    val = arabic_value(roman_letter)
    if val < stack.tos()
      stack.push(val - pop())
    elseif val == stack.tos()
      stack.push(val + pop())
    else
      stack.push(val)
    end
  end
  return sum(stack)
end

Note Yes, I am aware that might expose potentially embarrassing belief systems I might be operating under regarding life, the universe and everything. Meh.

I did some mental as well as paper-based run-throughs of the algorithm to convince myself that it would work and then implemented it in VimL. I don’t have the VimL solution to show you because after getting a working version I began refactoring it as I saw opportunities and generalities lurking within the code.

Here is what I ended up with:


function! Roman()
  let obj = {}
  let obj.values = {'M':1000, 'D':500, 'C':100, 'L':50, 'X':10, 'V':5, 'I':1 }

  func obj.to_arabic(str) dict
    let self.prior = 0
    return eval(join(map(
          \ reverse(map(split(a:str, '\zs'), 'self.values[v:val]'))
          \, 'self.calc(v:val)'), '+'))
  endfunc

  func obj.calc(v) dict
    let [n, self.prior] = [(a:v < self.prior ? -a:v : a:v), a:v]
    return n
  endfunc

  return obj
endfunction

So while my pseudo-code used a stack and processed the string in original order, the final algorithm reversed the string and just tracked the prior value each time through the loop (map()).

Some explanations of the weirder VimL:
  • I’m using VimL’s Object Oriented approach which is more like javascript’s than C++'s. The Roman() function is actually an object generator. Each such object then has a to_arabic(roman_string) method.
  • I’m also using VimL’s functional-ish approach to processing data lists. Reading such constructs is usually better from the inside out, which begins with:
    • The split(a:str, '\zs') yields a LIST OF individual LETTERS. Unfortunately, the explanation at :help /\zs doesn’t include the idiomatic use shown here of exploding strings out to a list of their component characters.
    • The map( LIST OF LETTERS , 'self.values[v:val]') converts individual Roman Numerals to equivalent Arabic numbers. This, therefore, returns a LIST OF NUMBERS. VimL’s map() function takes the list first and an expression to be evaluated against each element in turn (the resulting list returned by map() is the modification of each element through this evaluated expression). A simple example might help; this generates squares:

        echo map(range(1,10), 'v:val * v:val')
      The archaic looking `v:val` is VimL's Way of exposing access to the value of
      the element. If it helps, this would be equivalent to the Ruby code:

        (1..10).map {|val| val * val}
      Note map() in VimL is destructive, not that that matters here, though.
    • The map( LIST OF NUMBERS , 'self.calc(v:val)') inverts numbers in the list if they precede bigger numbers. Remember the original string order of letters is reversed in this algorithm.
    • The eval(join( LIST OF NUMBERS , '+')) sums the list.
All in all… I think this algorithm does what it’s supposed to do, and it does so with less aggression than solutions I googled for afterwards. o_O

At least the first comment in that last one tries to head the complainant in the right direction early.

The other thing I wanted to show today is micro-tests. Of course, being able to refactor sloppy code into a better solution is made practical with tests.

We all know and love testing, and Vim has decent plugins for doing it properly when you have to (i.e. writing your own plugin.) However, for quick’n'dirty jobs like this, I usually just drop a small inline test at the end of the script:


if expand('%:p') == expand('<sfile>:p')
  let fail = 0
  for t in [
        \  [1, 'I']
        \, [2, 'II']
        \, [3, 'III']
        \, [1992, 'MCMXCII']
        \, [1999, 'MCMXCIX']
        \]
    if t[0] != Roman().to_arabic(t[1])
      echo 'Fail: ' . t[0]
      let fail = 1
    endif
  endfor
  if ! fail
    echo "Ok"
  endif
endif

Note
  • The if expand('%:p') == expand('<sfile>:p') trick is to allow this script to be `:source`d from another script without triggering the unit-tests. I didn’t need this here because roman numeracy has been out of fashion for a while now, but I thought I would show this here as an added bonus.
  • I’ve shown few tests here to keep the post brief. My real tests for this little algorithm number in the fifties. I bet even then I’ve overlooked some poignant design aspect of Roman Numerals. This was meant to be an exercise in the general approach rather than aiming to provide an exhaustively accurate turn-key solution. One notable absence in that vein is the lack of data validation to ensure the roman numeral string is not of Celtic descent.
  • For those with the inability to read between the lines (osse), that last paragraph means: if I have flubbed some VimL, I’d love to hear from you; if I’ve merely upset dead Romans and their counting system, I don’t so much care.
Update: The testing code has been extracted out into a tiny little plugin called vim-u-test if you're interested in experimenting with it.