So I had this situation where I needed to delete an object from the database, if it existed. This is what I wrote:
obj = get_object(1)
obj.destroy if obj
And as I wrote this, it seemed a bit awkward to me. We always said Ruby had a really elegant syntax, but I guess I've come to the place where I find it a little grating. The thing about this example is the asymmetry of it. Conceptually I feel like the if check goes with the assignment, but it's separated from it in a strange way.
if obj = get_object(1)
obj.destroy
end
This seems better to me on the surface, but there are a few issues. In irb it warns that you're using =
when you might have meant ==
. This could potentially look like a typo to people reading the code as well. Another issue is that obj
is still in scope after the end of the if when appears like it wouldn't be. Finally, and of course this is highly subjective, but I find the end
keyward to be a little ungainly following a single line like this. I'll leave it to you to imagine the 3rd option with the assignment on its own line.
So I thought I would look at how you would do this in a couple of other languages I'm familiar with.
Object *obj = GetObject(1);
if (obj != NULL) {
DestroyObject(obj);
}
So that's kind of the standard for how to structure this kind of code. I like the cadence and the explicitness. And of course, it's very traditional.
var obj = getObject(1);
if (obj) {
obj.destroy();
}
Basically the same as C with some scripting language sugar. Java would be halfway between C and Javascript.
if obj := GetObject(1), obj != nil {
obj.Destroy()
}
I think this one might actually be my favorite. The inline assignment is an intentional feature of the language, and doesn't pollute the containing scope. The nil check is explicit, which doesn't seem like a loss here.
(when-let [obj (get-object 1)]
(destroy-object obj))
So when-let
is built in, and is designed for precisely this situation. It doesn't pollute the outer namespace, and it's all wrapped up with parentheses in a nice kind of way. (Actually if-let
would be equivalent in this situation. The difference is how multiple statements are handled.)
getObject 1 >>= lift destroyObject
As is often the case, Haskell is both the obvious winner and the obvious loser. Winner because, well, this is by far the shortest. There's virtually no syntax. And it's point-free, so we don't have to name obj
anything. Beautiful.
The downside is that Haskell examples are always a little bit of a lie. This one assumes you wraped the result of getObject
in a MaybeT
. If you were using the more obvious IO (Maybe Object)
you'd have to branch on the result.
do maybeObj <- getObject 1
case maybeObj of
Nothing -> return ()
Just obj -> deleteObject obj
And if your framework had some other monad stack, you would probably have slightly different code. (I mean I haven't worked in a real Haskell system, but that's my impression.) So the fact that this code is far more contextual than the other examples is a pretty big caveat in my opinion.
The other, obvious criticism is that this code is completely unreadable to the uninitiated, whereas all the other examples are pretty close to pseudocode, which does count for something.