The Electro-Future

Where is my flying car?

One of my ulterior motives for taking up Emacs again was to have an excuse to work in lisp (at least a little bit). Everybody seems to be raving about functional languages again, and they are one of the things I never really “got” in college (and never pursued afterward).

At work I was trying to switch to use Emacs for working on C# projects, and I thought it’d be really nice if I could C-x C-e to compile the project. The only problem is that, to rebuild the whole project, you need to run MSBuild on the “Solution” file, but that often won’t be in the same directory as the file you’re editing.

It seemed like a simple enough problem to search “up” from the current directory to find the first Solution file, and then pass that in to MSBuild. One of the rules I set up for myself, though, was to try to write it as “functionally” as possible (i.e. by not declaring any variables). My first attempt looked something like this:

(defun find-solution (dir)
  (if (directory-files dir nil "\.sln$")
      dir
    (if (not (equal dir (expand-file-name ".." dir)))
	(find-solution (expand-file-name ".." dir))
      )
    )
  )

Which returns the directory containing the file, but not the solution file itself. That if statement is also pretty ugly and isn’t very DRY, since it’s repeating expand-file-name.

First thing’s first is to get the directory and the file. If I was writing it in Python, it would look something like:

match_files = ... # filter with some lambda in it
if match_files is not None:
	return os.path.join(dir, match_files[0])
else:
	find_solution(..)

Which, despite having the word “lambda” in it, isn’t very functional (it has a variable in it!) To get around that, I cheated and decided to push that logic to a function:

(defun file-if-exists (file dir)
  (if file
      (expand-file-name file dir)
    nil
    )
  )

(defun find-solution (dir)
  (or
   (file-if-exists (car (directory-files dir nil "\.sln$")) dir)
   (if (not (equal dir (expand-file-name ".." dir)))
       (find-solution (expand-file-name ".." dir))
     )
   )
  )

Looking at the implementation of file-if-exists, it seemed like a pretty useful operation. I Googled around for a standard implementation, but couldn’t find one, so I wrote my own (taking the function to apply as an argument).

It didn’t seem like the best solution. Really, I’m cheating: the parameters are like variables, and named-functions are definitely cheating! I pretty much gave up, and moved on to tying it into csharp-mode, when (while looking for something else) I was reminded of the mapcar function.

Of course! I use map all the time in Python and C# (where it goes by the SQLish Select), and it even comes from the functional language world. Here it is again, with the map:

(defun find-solution (dir)
  "Search up the tree until you find a .sln file"
  (or 
   (car (mapcar (lambda (file)
		  (expand-file-name file dir))
		(directory-files dir nil "\.sln$")))
   (if (not (equal dir (expand-file-name ".." dir)))
       (find-solution (expand-file-name ".." dir)
		      )
     )
   )
  )

Now there’s only that if statement left, but what I really want to get rid of is the repeated call to expand-file-name. Lets see if mapcar can save us again:

(defun find-solution (dir)
  "Search up the tree until you find a .sln file"
  (car
   (or 
    (mapcar (lambda (file)
	      (expand-file-name file dir))
	    (directory-files dir nil "\.sln$"))
    (mapcar (lambda (upper)
	      (find-solution upper))
	    (delq dir (list (expand-file-name ".." dir)))
	    )
    )
   )
  )

It feels a little dirty to be using delq and mapcar on something that isn’t naturally a list (a single string; note that I have to explicitly create the list), but maybe that’s just my unfamiliarity with the language. They did name it Lisp for a reason.

Since this is my first time out with Lisp, let me know if you can think of a better way to do this. I’d love to hear about it!

Comments

Since I first started using Linux, my editor of choice has been Emacs. What first drew me to it was my ability to just start typing into it (unlike, ahem, some other text editors), and what kept me with it was the fact that I was essentially using a programming language to edit text.

Over the last few years, though, I drifted away. At work, I started working on an exclusively-Windows product, and at home I bought a Mac. One thing about Emacs is that it is, first and foremost, a console application; yes, it does have X11, Windows and Mac UIs, but the integration into those environments doesn’t always feel complete (for example, the editor’s and the OS’s copy/paste buffer isn’t always the same). When you spend a lot of time editing files, your text editor needs to be seamlessly integrated into everything else you do, and Emacs didn’t always feel like it was.

Of course, the elephant in the room when it comes to Windows development is Visual Studio. If you’re going to use Microsoft’s toolchain, you’re going to use Visual Studio and its text editor.

In the last couple versions, Microsoft has re-implemented the internal build system as MSBuild (their version of Ant). This makes it much easier to do builds from the command-line, but project management is still a nightmare outside of the IDE. If you want to add a new source file to your project, you have to edit a “project” file, which uses a very verbose XML schema; if you want to add a new project to the “solution” (think recursive Make), then you have to edit the solution file, which is implemented in some weird custom syntax and requires 256-bit GUIDs to be generated and assigned to each project.

The thing about Visual Studio is that I hate it. And every week, it does at least one stupid thing that makes me hate it more. After 5 years of using it, first for the Windows half of cross-platform C++ projects, but then exclusively for a large, Windows only C# project, I’m getting fed up. I’m fed up with a giant text editor that takes up 100s of MB of ram per instance (and I usually have to have about 5 instances or so open at once). I’m fed up with waiting 5 minutes while the GUI is locked so it can re-parse all its XML files when they get updated by SVN. I’m fed up with not being able to search for files by their name! In the back of my mind, I’ve been thinking: “How hard could it be to write a little Python script to add a new source file to the project XML?”

And this brings me to the Pragmatic Programmer. In the last few weeks there have been a few postings on Hacker News restating a lot of the advice from The Pragmatic Programmer, which has me thinking about “using a single editor well.” Over the past year or so I’ve been working on more projects at home again, which means programming on a Mac. I bought a copy of TextMate, and I really like it. But it’s not the same as Notepad++ in Windows, and it’s not the same as Emacs in Linux, and the more I start switching back and forth again, the more I want to stay in one editor.

So this weekend I installed Emacs via homebrew. I also got package.el, which is pretty cool, and included in Emacs 24 (I wish I had it a long time ago; maybe I wouldn’t have left the fold). There are a few things I never really learned, even when I was using Emacs pretty consistently: I never really got regions down, and never really focused on learning lisp/elisp well enough to really take advantage of a lot of Emacs’s power. I’ll try installing it again at work, and, maybe someday, I’ll have enough time to write that little script and get rid of Visual Studio for good.

Comments
Check me out