Thursday, October 8, 2009

Nested Lists in Vim

A while back, I worked on a project called Simplist. It was a little web app for creating, managing and sharing infinitely nestable lists.

It was really handy, and I was very proud of myself, until I realized that this is something you can do in Notepad with little effort and in vim with even less.

So I created a short script to duplicate Simplist's basic features in vim:



Notice that some items are marked as completed ("x") and some have priorities (numbers). The last item is actually a list itself with sub-items and is folded.

With a few simple commands, it's pretty handy:

,n - create a [n]ew list item
,x - mark item as done ("x")
,p - mark item as in [p]rogress ("=")
,- - mark item as undone ("-")
,N - mark item with priority N where N is 1-5
,t - update item's [t]imestamp

If you combine these with a few vim keys:

>> - indent line (make it a sub-item of item above)
<< - outdent line (make it a super-item)
zo - open a folded list
zc - fold an open list

you get a pretty decent way to manage lists. I use this all the time--every day at work and for personal lists.

Here's the vim script. Just paste it into your .vimrc:

if version >= 700
autocmd BufNewFile,BufRead *.list call ListFile()
autocmd TabEnter *.list call ListFile()

" 'install' list features
function ListFile()
setlocal foldmethod=expr
setlocal foldexpr=ListFoldLevel(v:lnum)
setlocal shiftwidth=4
setlocal tabstop=4
setlocal foldtext=ListFoldLine(v:foldstart)
setlocal noshowmatch
setlocal cindent
" add [n]ew item below current
map ,n o- =ListTimestamp()^la
" mark item as [x]
map ,x mz^rxf[hdf]$a=ListTimestamp()`z
" mark item as [-]
map ,- mz^r-f[hdf]$a=ListTimestamp()`z
" mark item as = (in [p]rogress)
map ,p mz^r=f[hdf]$a=ListTimestamp()`z
" mark item with a rank
map ,1 mz^r1f[hdf]$a=ListTimestamp()`z
map ,2 mz^r2f[hdf]$a=ListTimestamp()`z
map ,3 mz^r3f[hdf]$a=ListTimestamp()`z
map ,4 mz^r4f[hdf]$a=ListTimestamp()`z
map ,5 mz^r5f[hdf]$a=ListTimestamp()`z
" add/update [t]imestamp
map ,t mz$a [^f[hd$a=ListTimestamp()`z
endfunction

" return properly formatted timestamp
function ListTimestamp()
return ' ['.strftime('%Y-%m-%d %T').']'
endfunction

" return fold line format
function ListFoldLine(linenum)
let s:count = 1
let s:spaces = ''
while s:count <= &shiftwidth
let s:spaces = s:spaces.' '
let s:count = s:count + 1
endwhile
return substitute(getline(a:linenum),"\t",s:spaces,'g')
endfunction

" foldexpr function
function ListFoldLevel(linenum)
let s:prefix = ''
let s:myline = getline(a:linenum)
let s:nextline = getline(a:linenum+1)
let s:mynumtabs = match(s:myline,"[^\t]",0)
let s:nextnumtabs = match(s:nextline,"[^\t]",0)
if s:nextnumtabs > s:mynumtabs " if this item has sub-items
let s:level = s:nextnumtabs
else " next item is either same or higher level
let s:level = s:mynumtabs
if s:nextnumtabs < s:mynumtabs " if next item has higher level, close this fold
let s:prefix = '<'
let s:level = s:nextnumtabs+1
end
endif
if a:linenum > 1
s:pline = getline(a:linenum-1)
s:pnumtabs = match(s:pline,"[^\t]",0)
if s:level < s:pnumtabs
" if this is higher level than prev, start a new fold
let s:prefix = '>'
endif
endif
return s:prefix.s:level
endfunction
endif

Thursday, September 24, 2009

A Different Way: Traits in PHP

For a while now I've had some friends who are Perl fanatics. Perl's OOP support is minimal, which makes it extremely flexible and powerful. One module that takes advantage of that is Moose, which provides an easy-to-use framework for OO design. Basically, Moose gives Perl the OO capabilities that most languages take for granted, if that's what you're looking for. In addtion to such capabilities are a few that most languages don't have, such as Roles.

Moose Roles are:

  • like multiple-inheritance, except that a Role handles method and attribute collisions

  • like interfaces, except that a Role is more than a contract--it's fully-working code

  • like mixins, except that Roles can have requirements for being applied to an object

  • like duck typing, except that a Role is a named collection of methods and attributes, so the state of being something is more concrete


These ideas really inspired me, but I'm stuck with PHP. Fortunately, PHP 5.3 comes with a crippled implementation of lambdas and closures, which gives just enough functionality to implement something similar to Moose::Role. I also read these two papers, which gave me some solid ideas, although I didn't read them as gospel.

You can get the code from GitHub.

How It Works

First, you have to define some traits:

TObj::Trait('Sortable',
'sort',function($obj) {
...
},
'sortAttribute','',
'setSortAttribute',function($obj,$attrName) {
$obj->sortAttribute = $attrName;
}
);

TObj::Trait('Categorizable',
'category','',
'getCategory',function($obj) {
return $obj->category;
}
);

Now we create some other class:

class Something extends TObj {
private $foo;

public function __construct($baz) {
$this->foo = $baz;
}
}

And now we apply our traits to Something object:

$something = new Something('lolwut');
$something->apply(Sortable);
$something->apply(Categorizable);

You can now call those traits' methods as if they belonged to the Something class! Note that an object's methods and attributes will override any methods or attributes in the traits, even within the context of the calling trait (see part about aliasing below). This makes traits as flexible as extended super classes.

It's a fairly simple concept, though there are some hang-ups when you get down to the nitty-gritty. For instance, what if two traits have methods or attributes with the same name? Well, you can simply exclude it from one of the traits:

$something->apply(Categorizable,array(
except=>array('getCategory')
)
);

or alias it:

$something->apply(Categorizable,array(
alias=>array('getCategory'=>'getCategoryName')
)
);

But what if getCategory() is used all over in other Categorizable methods? Now they'll actually be calling the getCategory() from the other trait, right?

Wrong! Since TObj has to handle all trait method dispatch, it does a little magic. To resolve method and attribute names, it checks for the original name ("getCategory") within the trait whose method made the call, then moves outside and checks aliased and unaliased names. It works the same way for trait attributes, too. This prevents aliases from breaking trait code.

To know if some random object you've received has a certain trait:

if ($something->applied(Categorizable)) {
echo 'It is categorizable!';
} else {
echo 'This object marches to the beat of a different drummer.';
}

To require that the object have a certain trait method:

TObj::Trait('TraitThatRequires',
required,array('aRequiredMethod'),
...
);

$something->apply(TraitThatRequires);

The last line would throw an exception because aRequiredMethod() is not yet implemented by a trait applied to $something.

TObj traits do other things, and you're welcome to check out its unit tests to see its full functionality.

This project is very new, and it has little or no documentation and I'm sure it has bugs (though all tests pass, of course). If you're interested in the project, feel free to pull it and contact me if you have any interesting ideas.

Wednesday, September 2, 2009

Vim Tip: Debug Path

I work for a company whose code is in thousands of files in hundreds of directories. We have multiple VCS repositories. Function libraries, class files, HTML, XSL--a convoluted maze to navigate, especially when trying to debug something.

Whenever I'm debugging, I may follow the rabbit hole from function to function, through various libraries and classes, only to find that I'd taken a wrong turn somewhere and come to a dead end. I then either have to sift through the multiple tabs I've opened to find where I should pick up the search again, or, if I've been closing files along the way, I have to start the search from the beginning. This is annoying and time-consuming.

I decided to write a simple vim script to save my cursor's current line, along with the file name and line number, to a file in my home directory. I can then continually save lines as I follow a path through the code. If, at any time, I hit a dead end, I simply go through this "trace" file and find where to pick up the search again. Combined with vim's "gf" command, this file is very useful!

Here's the code you can add to your own .vimrc file:

nmap <F8> <ESC>:call WriteTrace()<CR>

function WriteTrace()
let lineNum = line('.')
let lineFile = bufname('%')
let lineVal = getline(lineNum)

let allLines = readfile("$HOME/trace.txt")
let allLines = add(allLines,lineFile.":".lineNum)
let allLines = add(allLines,lineVal)
let allLines = add(allLines,"")

call writefile(allLines,"$HOME/trace.txt")
endfunction

In the code above, F8 will save the line under the cursor, but the nmap can be changed to map to any key, of course.

Wednesday, August 26, 2009

PHP Is a Husk

I thought there was hope. I really did. Maybe it was naivete, or simply denial because it's the language I use day-in and day-out. But there is no hope for PHP.

No, I don't think the project will die; I'm sure it will continue to grow. It's no new revelation that the language has a lot of problems. Even when we finally got prepared statements with the MySQLi interface, there were a lot of problems with the way it worked such that writing a thorough DBAL for it was nearly impossible. But lately I've been pretty positive about the direction PHP has been going--with 5.3 came namespaces, lambdas and closures, among other things.

And then I started playing around with these new features. The namespace delimiter is odd, but big deal. Lambdas and closures, though, are broken and useless for anything but the most basic applications.

For instance, I've been messing around a little with Perl and an awesome OO extension for it called Moose. Moose has a ton of features, but one particular feature is Roles. Roles, as defined by the Moose devs, are somewhere between abstract classes and mixins, with multiple inheritance--but more flexible and easier to develop with. You know, pure awesome.

So I set out to try my hand at making something similar to Roles with PHP's new lambdas and closures. That should give me all the power I need to hack something together, right? This is the best I could come up with:


// class used to enable roles for other classes
class Role {
// used by the class the role is applied to
// must be in that class's __call() method
static public function call($obj,$funcName,$args) {
$func = $obj->$funcName;
array_unshift($args,$obj);
call_user_func_array($func,$args);
}

// applies a role to the object.
// for best results, use in the constructor.
static public function apply($obj,$name) {
$mixin = new $name();
foreach ($mixin as $funcName=>$func) {
$obj->$funcName = $func;
}
}
}

// my example role that I want to apply to other classes
class MyRole {
public $yar = 'role\'s test string';

public function __construct() {

// prints a string, including a var from the object
// that is using this role.
$this->roleFunction = function($obj) {
print('This is a role function! '.$obj->blah."\n");
};

// prints the object that is using this role.
// this will also show the methods it has received
// from the role.
$this->printr = function($obj) {
print_r($obj);
};

// a function that gets overridden by Test class's
// native method by the same name.
$this->overriddenFunction = function($obj) {
print("You'll never see this.\n");
};
}
}

// my class that I want to apply a role to. Note the
// call to Role::apply() in the constructor.
class Test {
public $blah = 'test string';

public function __construct() {
Role::apply($this,'MyRole');
}

// overrides MyRole's version, as it should
public function overriddenFunction() {
print("I beat the role's method!\n");
}

// necessary mess to get $object->roleMethod() to work
public function __call($funcName,$args) {
Role::call($this,$funcName,$args);
}
}

$test = new Test();
$test->printr();


The above will print_r the Test object, as it should, including the public attributes that hold the lambdas from the Role. Neat, right?

Well, not really. This is so messy, so impractical. The limitations are ridiculous. For example, the following works:


$someFunc = function() { print('Hello, blog!'); }
$someFunc();


But this doesn't:


$obj = new StdClass();
$obj->someFunc = function() { print('Hello, blog!'); }
$obj->someFunc();


Whoops! That object method doesn't exist! And what about this:


class MyNeatoClass {
public $someVar = 'Hello, blog!';

public function __construct() {
$this->someMethod = function() { print($this->someVar); }
$this->otherMethod = function() use ($this) { print($this->someVar); }
}
}


Neither the lambda nor the closure works. $this cannot be used in lamdas or closures. So, when it comes to using these new features with objects, they're quite nearly completely useless. These are just a couple examples of what makes my above attempt at creating Roles broken and useless.

And as I mentioned before, this isn't the first time the PHP devs have botched up new features. They seem to be committed to adding "big boy" features, which should be a good thing, but not to building them correctly. This leaves developers--who actually know what these features are and how to use them in other languages--frustrated and disgusted. For what audience are they coding? Do they think Grandpa Jo's 14-year-old nephew, who "builds websites", is going to use closures? No, real developers are going to try to use them, and then give up, because they're useless.

This trend is obviously going to continue. PHP had the opportunity to recover from past mistakes with recent new features. They have not learned a thing from past mistakes. PHP, you are dead to me.

Sunday, August 23, 2009

Chaos vs. Best Practice: Let Them Eat Cake

Even if you didn't know who _why was a couple weeks ago, most likely you've gathered enough since then to know that he was a very bright and artistic programmer (or a very bright and logical artist?) who inexplicably erased his digital presence.

But enough about _why, as that dead horse has been and is currently being beaten to horse pudding. However, in looking through some of _why's archives (things are rarely truly erased), I found this insightful and interesting blog post.

To sum up, _why feels like so called "best practices" are dead, boring, and stifling. Good, artistic and revolutionary code comes from chaos. Diagrams and TDD are simply scripted banality. I see his point, but I responded to his post thus:


Refusing to be stifled by best practices is one thing, and it’s not a new thing; to touch on the gagging cliches, everyone who’s ever caused a shift in perception or the field of possibilities--Beethoven, Lennon, Newton, DaVinci--questioned the “best practices” of their field in their day. But you mentioned yourself that we can’t possibly understand every avenue, every corner, of the systems we work with. Isn’t that why we have best practices? To invent the wheel without understanding friction coefficients is foolish. Even though you disagree with their choice, maybe your predecessors decided a sleigh made more sense because you didn’t realize that you live in North Dakota.

Chaos is for after-hours. Chaos is a bunch of possibilities and fewer probabilities, with only one or two sure things. Once you’ve polished all that and have one sure thing in your hands, use it at your day job. At that point, it’s a best practice. Best practices have an origin. Chaos has a conclusion; to claim to subscribe to one and reject the other is short-sighted. To accept both as part of the process shows your passion and dedication to your field.


To expand on this response, there are three types of programmers with respect to chaos and best practice:

  • Great programmers who can dabble in chaos, confident that a best practice will rise from the ashes of many smart--but failed--attempts. They then share and use these practices. These are the French elite of programmers.

  • Decent programmers who dabble in chaos but do not develop best practices. Such dabbling helps them to see the wisdom in best practices and use them wisely to make solid software. These are the French bourgeoisie.

  • Programmers who shouldn't be programmers--the peasant farmers.


I appreciate _why's contributions to programming-as-art. And I really believe that he is brilliant enough to develop best practices from chaos. But his advice to abandon best practice for chaos is saying to the rest of us, "Let them eat cake!" I'm sure his elitism is unintentional, and I certainly don't mean to villianize him by any means. But cake is a luxury and an artistic tribute to the beauty of food. Buildings are built by men eating meat and potatoes. Solid, robust and maintainable software is developed by programmers who use best practices.

Thursday, August 20, 2009

Vim Tip: Session Management

Instead of having another gigantic essay "Vim Tips, part 2," I decided to just address interesting tid-bits of my .vimrc file one at a time. First up: sessions!

Vim sessions are rudimentary but can easily be enhanced to make working on a project or multiple projects more convenient. I'm a big fan of tabs, so I'll often have several tabs open at a time. When I return to work the next day, I'd like to pick up where I left off as seamlessly as possible. Also, if I get pulled away on something else temporarily, I'd like to be able to save my current state, jump to the task at hand, and save its session, as well, in case I have to go back to it again after it's complete.

To this end, I've put the following in my .vimrc file:

nmap <F3> <ESC>:call LoadSession()<CR>
" don't store any options in sessions
if version >= 700
set sessionoptions=blank,buffers,curdir,tabpages,winpos,folds
endif

" automatically update session, if loaded
let s:sessionloaded = 0
function LoadSession()
source Session.vim
let s:sessionloaded = 1
endfunction
function SaveSession()
if s:sessionloaded == 1
mksession!
end
endfunction
autocmd VimLeave * call SaveSession()

To start a session in vim, use the following:

:mksession

This will create a Session.vim file in your CWD. Now, next time you open vim, you can simply hit F3 and your session will be restored. Of course, this little snippet does more than just ":source Session.vim".

When you hit F3, my script sets a flag that means we're currently using a saved session. When you quit vim, it will automatically re-save your session, so when you restore it next time (by hitting F3 again), vim will be returned to the exact state it was in when you quit. It's a very simple hack, but it saves a lot of time.

One improvement I'd like to make is to be able, somehow, to select which session file to use. Suppose I have Project1.vim and Project2.vim ("Session.vim" is the default name, but isn't required). I'd like to be able to select which project I'm working on, and the appropriate session file would be loaded.