Showing posts with label Syntax. Show all posts
Showing posts with label Syntax. Show all posts

Sunday, August 5, 2012

Irritation

Just a short update this time, involving things I keep stubbing my toe on in Lisp and Erlang.

Common Lisp is not Object Oriented

The object orientation support is bugging me again. Not just me, either[1], because a bunch of modules I've been making use of lately have functions with names like time-difference or queue-push, which is precisely what the generic functions are supposed to save you from doing. It recently annoyed the fuck out of me while putting together a simple, caching implementation of a thread-safe queue. I wanted that construct to have push, pop and length, but because those names already designate top-level functions, it's not quite as simple as declaring them.

I'm not about to be dumb enough to propose that this makes Common Lisp an unacceptable language, especially since it looks like this could easily be fixed within the spec as it exists today, and I already quasi-proposed a semi-solution. I just have to give voice to that minor frustration, and point out that what you'd really want in this situation is access to a lot of the basic CLHS symbols as methods rather than functions. Not having this has now bitten me directly in the ass no less than twice[2], and signs that it might be worth fixing are showing in various CL libraries.

Erlang Should Be More Like JavaScript

Wow, do records suck donkey dong!

Ok, to be fair, they're better than having to deal with plain tuples when you're working with large constructs, and they're arguably The Right Way to deal with database storage, but they're a fundamentally annoying and hacky way of implementing key/value pairs.

The problem is record sharing. Here's a thought exercise: what happens when you have a system that deals with the storage and manipulation of sets of comments[3], and a second, completely separate system which would like to consume the output of that first one in order to display these sets in interesting ways for human consumption?

If you had a real k/v construct built in, like what every other goddamn language on this earth seems to have, what you would do is pass an instance of that construct across.

If the hash map was a fundamental data type in Erlang, you would have no problem in this situation.

But.

Records are basically tuples, wearing a bunch of reader macros and syntactic sugar. That means they're potentially faster than using a dynamic data structure for the same purpose, but it means that you can't just pass a record between two otherwise decoupled systems. If you want the same sort of behavior that you'd get out of native k/v support, you have three options I can see, and they all make me want to glare menacingly at Joe Armstrong, or at least whoever decided that records were a satisfactory solution.

Option 1: Duplicate Records

You declare the same record in both systems, then send records across.

This sucks balls because changing the record suddenly requires you to change and recompile both projects. They are not really decoupled anymore. In our theoretical example above, say we've decided that we'd really like to start tracking comments hierarchically. We need to add a pair of new fields, root and parent so that each comment can tell you which tree its part of and where in that tree it is.

-record(comment, {id, user, thread, root, parent, timestamp, title, body, status}).

Now, we can't just make this change in the model component, because if you had different record declarations in the model than the view, you'd get compiler errors. If you have multiple views trying to make use of the same model, and not all of them need the new data[4], too fucking bad, you're changing them all over anyway. This isn't even the worst case scenario, by the way. If you decide that the record shouldn't change fundamentally, but that you merely need to reorder fields, you won't even get a compiler error if you forget to change records in both places.

This is not the sort of brittleness that I expect from a key/value construct.

Option 2: Shared Records

You can write one file, lets say records.hrl, put all your record declarations in there and then include that file in both projects.

This sucks balls because now you don't actually have two decoupled projects at all. You've got one giant, mostly disjoint project with shared data declarations. It's not horrible, to be fair, but remember that having a run-time construct rather than a compile-time record system wouldn't even require this much additional planning.

Option 3: Sending Tuples or Proplists

This is the option I went with for a recent project, and I'm honestly not sure it was the right approach, but there would have been record name collisions otherwise, so whatever, I guess.

Instead of sending records between components directly, you emit a tuple from the model and consume it in the view, potentially creating an intermediate record if you need to. This has pretty much all the downsides of Option 1, except that you don't have a single record name-space to deal with. If you take the Proplist approach, it gets very slightly better because you only need to put together the one abstraction layer to do look-ups, and if you make it complete enough, you don't need to change it whenever you change the record definitions. That's still a lot more annoying than just having this force pre-resolved.

I remember writing up notes from a talk Joe gave about Erlang. One of the points he covers under the "Missing Things" heading was Hash Maps, wherein he pointed out this specific issue with the fundamental architecture of the language. In the notes, I sort of acknowledge that he has a point, but don't linger on it too long. Honestly, I was thinking that it wouldn't bite at all, let alone as hard as it actually has. Joe, if you're reading this, you were right. And for the love of god, if you've got a solution in mind, DO IT.

lists:keyfind/3 and workarounds like this aren't nearly as satisfying as just having an actual, dynamic key/value construct built into the language from the ground up.


Footnotes

1 - [back] - Though I may be the only one who's noticing enough to bitch about it.

2 - [back] - That I've noticed.

3 - [back] - -record(comment, {id, user, thread, timestamp, title, body, status}).

4 - [back] - For instance, if there are places that you're displaying the same set of comments, but don't really care about their hierarchy.

Sunday, October 16, 2011

Ruby vs. Python Briefly

Ok, so I figure it's about time to live up to the title of this blog, since I've spent the vast majority of the language discussion firmly planted in parentheses. Aside from the fact that my company is starting a project in Erlang, I've also been scripting Python and Ruby pretty heavily.

They're actually not very different languages. Neither is perfect from my perspective[1], and neither sucks. If I had to, I could get work done in both (and having gone through the Ruby chapter in 7 Languages in 7 Weeks, I'm more inclined to look at Ruby for my next big project than I used to be). To start with, here's a boiled down, no-nonsense table that represents my perspective.

...is more annoying than...

len([1, 2, 3])
[1, 2, 3].length
"foo " + bar + " baz"
or
"foo %s bar" % bar
"foo #{bar} baz"
", ".join(["one", "two", "three"])
["one", "two", "three"].join ", "
map(lambda a: a + 1, [4, 3, 2, 1])
## still makes more sense 
## than join or len, though
[4, 3, 2, 1].map {|a| a + 1}
a = [4, 3, 2, 1].sort()
a[0]
[4, 3, 2, 1].sort[0]
nothing.jpg foo.methods.sort
require 'optparse'
require 'pp'
require 'fileutils'
import optparse, fileutils
## I also prefer the more granular 
## symbol access I get with python
sudo apt-get install ruby-full
irb
python

...is about as annoying as...

def aFunction(foo, bar):
    #do stuff
    return baz
def a_function(foo, bar)
  #do stuff
  baz
end
with tempfile.NamedTempFile() as tmp:
    tmp.write("Test test\n")
    ##more stuff    
    tmp.flush()
    popen(["lp", "-d", "a-printer", tmp.name()])
Tempfile.open() do |tmp|
   tmp.write("Test test \n")
   ## more stuff
   tmp.flush
   system("lp", "-d", "a-printer", tmp.name)
end

So I am slightly biased, but like I said earlier, not enough to actually decry either language. The biggest point in Ruby's favor is its handling of blocks (as seen in that tempfile pseudo-code). I like having an expression that says "Create an entity, do this stuff and then clean up", without having to clean up myself. Python doesn't like that.[2] Gotta admit, I boggled at the join syntax the first time around. Rhetorically, who the hell decided it makes sense that a join operation is something you do to the delimiter, rather than the list? In my opinion, it would even make more sense to make it a standalone function a-la len.

I really like the syntactic whitespace in Python.

def something():
    foo()
    bar()
seems like it's cleaner than the Ruby equivalent. Except that when I want to return the result of bar (which I do quite often, given that I much prefer functional programming to OO), I need to do so explicitly. Ruby has me waste an additional line on end, but returns implicitly. While I'm at it, Python libraries seem to be heavily anti-functional programming. They do the standard "OO" thing of exposing functionality via classes, but they also typically have a heavy reliance on side effects, which makes it harder than it ought to be to compose things. A recent example I had to go through involved using pyPDF and reportlab to process existing PDFs. You can do it, but the amount of fiddling involved is nontrivial if you want to decompose the problem properly because you need to do so by setting up multiple instances of PdfFileReader/canvas and making destructive changes to them.

Also, not represented in the table is how much easier it is to install python packages in Debian. While gem frequently errors on something, I've yet to find a package I need that I can't either apt-get or retrieve using python-setuptools. That's worth something (in fact, it's worth enough that I've been procrastinating on a ruby port of get-youtube-series, which used only default components in Python, but requires several installs in Ruby).

The last thing that table doesn't encompass is the version situation. That's a fairly major one from my perspective, but I'm not sure how serious it actually is. Python 3 has been out for quite a while, but it's not uncommon to see "Supports Python 2.7" on various frameworks/utilities. Squeeze still provides 2.6.6, Django still requires 2.[4-7] and Google app-engine is still asking for 2.5 (with 2.7 being supported as an "experimental" feature). That's less than encouraging. By contrast, Ruby 1.9 is fairly widely supported (though the Debian repos are still at 1.8.7). That just doesn't seem to bode well for the next version, regardless of how enthusiastic Rossum is about it.


Footnotes

1 - [back] - Though, to be clear, my opinion is that Ruby gets a damn sight closer than Python.

2 - [back] - Thank you Brendan Miller for pointing me to the with statement (documented here, here and here) which does emulate blocks well enough for my purposes.