Tuesday, August 27, 2013

Not Classy, VimLPOO!

Rise, fellow VimLers and cease sobbing onto your consoles about the stink of VimLPOO (VimL Programming Object Orientedly). Vim keeps an open mind and so should you.

tl;dr : VimL OOP can haz class-reopening like Ruby

Okay, maybe not exactly like Ruby, but check it:

VimL’s OOP is more like javascript’s than Ruby’s. It doesn’t have explicit classes. It uses dictionaries to store data and methods that operate on it.

Here is one way in VimL to create an object factory:

function! Kid(name)                  <1>
  let k = {}                         <2>
  let k.name = a:name                <3>
  func k.say(blah) dict              <4>
    echo self.name . ': ' . a:blah
  endfunc
  return k                           <5>
endfunction

let boy = Kid('Jack')                <6>
let girl = Kid('Jill')
call boy.say('wassup?')              <7>
call girl.say('chillin'' at the hill. u?')
echo boy                             <8>
Jack: wassup?
Jill: chillin' at the hill. u?
{'name': 'Jack', 'say': function('69')}
  1. I like to use the full command form function when creating object factories (classes?).
  2. The object container is a dictionary (hash).
  3. You can explicitly set attributes outside of methods if desired.
  4. I like to use the short command form func for methods. The dict argument tells Vim that this is an instance method, providing us the self. accessor.
    Note You don’t need the ! on method declarations as you do for the outer-level.
  5. The factory must return the newly created object.
  6. Create an instance using the factory.
  7. Call methods using dot notation.
  8. The object in its native format is just a dictionary (hash).
Typically, after creating the object factory, the VimLPOO developer can’t re-open it to augment its behaviour although you can derive a new factory type from an existing one (inheritance without paternity):

function! RudeKid(name)
  let rk = Kid(a:name)               <1>
  func! rk.say(blah) dict            <2>
    echo self.name . ': Yo, biatch! ' . a:blah
  endfunc
  return rk
endfunction

let boy = RudeKid('Jack')
let girl = Kid('Jill')
call boy.say('wassup?')
call girl.say('wtf?')
echo boy
Jack: Yo, biatch! wassup?
Jill: wtf?
{'name': 'Jack', 'say': function('72')}
  1. Base this object on the parent factory.
  2. Override methods as desired.
    Note The use of ! is now required because the method already exists in the base object.
But I’m not here today to talk about weak inheritance. I wanna play with class re-opening, Vim style.

As a quick recap, a Vim object is a dictionary with data and methods that can use the self. modifier internally to refer to its data and other methods. It turns out that Vim is not too particular about who gets to claim dict access on your objects. You’re free to create external functions, adorned with the dict modifier, and have them manipulate your objects as if they were created with the class originally:

function! s:slapped() dict
  echo self.name . " just got slapped!"
endfunction

This happens to be a script-local (s:) function; global scope would work too, but why pollute unnecessarily? Now, if you tried to do a naive direct call of this, you’d be sorely disappointed:

call boy.slapped()
Error detected while processing jack_and_jill.vim:
Line   42:
E716: Key not present in Dictionary: slapped

That makes sense… We created slapped() as a script-local function, not a method on the boy instance.
Note Adding the method to the Kid() factory after having created the boy instance would be just as useless.

Happiness is just a call away:

call call('s:slapped', [], boy)

" Jack just got slapped!

:-D How cool is that?!

I have a little project in the works that uses this to allow clients of the engine to inject their own solutions to various parts of the workflow. It’s almost done, so I should be able to show something a bit more real-worldy soon. For now, what mischief can you concoct with this shiny new toy? I look forward to finding out. :-)

Vim on!

No comments:

Post a Comment