First impressions on (Common) Lisp

Because it’s good to learn a new Language, because lispers seem convinced that it’s the most powerful language ever, and because it’s supposed to be some sort of programming religious experience[1], I decided to give Lisp a try.

Since I had sincere hopes of end up using it on professional projects, after looking around I leaned towards Common Lisp, and particularly the book Practical Common Lisp by Peter Seibel. After the first 9 chapters, I’m somewhat disappointed. I’m guessing this is partly because of my high expectations and partly because most of lisp’s “cool” features are getting less and less uncommon as languages advance[2]. These are my first impressions.

It’s not readable. Strangely enough I don’t mind all the parentheses nor the prefix notation; I get that they’re part of what makes Lisp so powerful and actually got used to it pretty quickly. Still, the language is overall pretty cryptic, even for things that could with no effort be a lot clearer (t/nil instead of true/false?). It might not be for experienced Lisp programmers, but, well, if it’s readable only for the experienced, then it’s not readable.

Some examples from the first chapters of the book:

(format t "~{~{~a:~10t~a~%~}~%~}" *db*)

and:

(defmacro where (&rest clauses)
  `#'(lambda (cd) (and ,@(make-comparisons-list clauses))))

Suddenly Perl isn’t looking that bad.

The operators set is huge. That’s not good news for me. I like languages that have a small, orthogonal, easy-to-remember set of built-in operators, and good libraries built on top of them. This is the case of C and Python, and not of Lisp, which seems to have loads of built-in operators, between special operators, functions and macros. In the first chapters of the book, every bit of code introduces a couple of new ones. This not only makes it hard to keep track of them, but without seeing a thorough reference, I have no idea on what to expect to be available as a built-in and what I’m supposed to write myself.

From what I read, that small core is there somewhere, but at least this books makes it look like it’s too low level to get anything done with it, and instead uses the macros built on top of it.

…and it’s not orthogonal. There seems to be way too many operators for the same purposes but with slightly different behaviors. The most evident case of this is the equality: =, CHAR=, EQ, EQL, EQUAL, and EQUALP. Seriously?

…and some names are really, really bad. MACROEXPAND-1, LET and LET* (a common pattern for slightly different operators), Y-OR-N-P, etc.

It’s not intuitive. As a consequence of the previous points, it gets really hard to guess my way through a piece of code; instead I have to look up most operators. Anytime a new operator was introduced in the book, it worked different from what I expected.

Macros look promising. They certainly do; after catching just a glimpse of what can be done with them, macros look like a powerful tool that one can get quickly used to, and will miss in other languages. I particularly enjoy the way they’re used in the book as the driving technique to constantly refactor code, built bottom-up.

I’m aware that most of this issues would go away after some time using the language, but taking in account the downside of being not so widely used as other languages (meaning less documentation, less libraries, less users, etc.) so far it’s not looking like one I’d use for more than one or two projects. It might make sense for me to check out some other Lisp dialects, like Scheme or Clojure.

[1] Eric Raymond, How To Become A Hacker.
[2] Paul Graham, Revenge Of The Nerds.

ifile.it API wrapper for Python

Here’s a Python wrapper I’ve made to upload files to the ifile.it servers using their API. The code sends the files using the poster package.

'''
Wrapper for the ifile.it API. Uses the poster api.
'''
import urllib2
import json
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers
import urllib

class Ifileit(object):
    """ Wrapper class for the ifile.it API. """
    
    UPOLOADED_FILE_URL = 'http://ifile.it/{ukey}'
    GET_UPLOAD_URL = 'http://ifile.it/api-fetch_upload_url.api'
    FETCH_API_KEY_URL = 'https://secure.ifile.it/api-fetch_apikey.api'
    PING_URL = 'http://ifile.it/api-ping.api'
    
    USER = ''
    PASSWORD = ''
    API_KEY = ''
    
    @classmethod
    def ping(cls):
        """ Returns True if the ifile.it server is up. """
        
        response = cls._open_and_check(cls.PING_URL)
        return response.get('message', '') == 'pong'
    
    @classmethod
    def upload(cls, the_file):
        """ 
        Uploads the_file to the ifile.it server. It should be a file-like 
        object as required by poster. 
        """
        
        #Needed by poster
        register_openers()
    
        post_data = {'Filedata' : the_file}
        post_data.update(cls._get_akey())
        datagen, headers = multipart_encode(post_data)
        
        request = urllib2.Request(cls._determine_upload_url(), 
                                                    datagen, headers)
        response = cls._open_and_check(request) 
        
        return cls.UPOLOADED_FILE_URL.format(ukey=response['ukey'])
    
    @classmethod
    def _determine_upload_url(cls):
        """ Gets the upload url from ifile.it API. """
        
        return cls._open_and_check(cls.GET_UPLOAD_URL)['upload_url']
    
    @classmethod
    def _open_and_check(cls, url, data=None):
        """ 
        Opens the given url string or Request object and checks for the status
        parameter in the response.  If it's 'ok' returns a dict with the 
        response. otherwise raises IfileitApiError.
        
        Data should be a dictionary of POST arguments or None for a GET request.
        """
        
        if data:
            data = urllib.urlencode(data)
        
        response = json.loads(urllib2.urlopen(url, data).read())
    
        if response['status'] != 'ok':
            raise IfileitApiError()
        
        return response
    
    @classmethod
    def _get_akey(cls):
        """ 
        If the user info is set, return the akey parameter for the POST API 
        calls. Otherwise returns an empty dict.
        """
        
        if cls.USER and cls.PASSWORD:
            if not cls.API_KEY:
                response = cls._open_and_check(cls.FETCH_API_KEY_URL, 
                                               {'username' : cls.USER,
                                                'password' : cls.PASSWORD})
                cls.API_KEY = response['akey']
            
            return {'akey' : cls.API_KEY }
        
        return {}

class IfileitApiError(Exception):
    pass