Namespaces
2 December 2009
00:12
Most programming languages get namespaces (or importing modules or files) wrong.
using System; using System.IO; public class HelloWorld { public static void Main(string[] args) { Console.WriteLine("Hello, World!"); } }
Where did that Console
class come from? System
? System.IO
? Is it somehow implicitly available? Just by looking at the code, without knowing the .NET framework, you can't tell. (Nor can you tell that the System.IO
namespace is not used anywhere, so that the corresponding using
statement can be safely removed. I think.)
You shouldn't have to ask this question, and you shouldn't have to rely on an IDE or documentation to provide the answer. It should be obvious from just reading or grepping the code. Languages that violate this simple rule include Java, C#, Ruby, and C/C++. I'm sure there are lots more.
UPDATE: Wow, it's been so long since I last hacked on Java code that I forgot how its imports work. Actually, Java imports don't violate the rule unless you use star imports, just like Python.
Turns out the languages I'm currently most interested in get this (mostly) right: Python, Erlang, Go, and (although not a language as such) node.js with its CommonJS-based module system:
var puts = require("sys").puts; puts("Hello, World!");
Sure, you can put Python into dirty namespace mode by using “star imports”, and you can intentionally mess up the explicit namespacing in node.js by applying the process.mixin()
method to pull exports into the global namespace. So please try not to do that.
Reactions
-
Christopher Lenz says:
2 December 2009
10:38Comments were broken, should hopefully be fixed now (though still with limited OpenID 2 support).
That's what I get for running custom blog software and only posting something once a year, I guess.
-
someone says:
2 December 2009
10:39Agree with this, generally, although I'd be careful about conflating importing modules with namespaces.
However I don't see that java violates this rule any more than python does. 'Don't use star imports' is well established.
-
Chris Mear says:
2 December 2009
11:09I'm not overly-familiar with the other languages in this post, but at least in Ruby there's a distinction between importing a library file, and importing the module. For instance, if you wanted to do some MD5 digests, you'd normally do something like
require 'digest/md5' digest = Digest::MD5.hexdigest("Lorem ipsum")
which retains the explicit namespacing in your code. You could do the wrong thing, like this:
require 'digest/md5' include Digest digest = MD5.hexdigest("Lorem ipsum")
which will technically work, but in most contexts would be considered stylistically poor form.
I guess it's more clunky to have to manually specify actual files, instead of having them automatically resolved for you by an import-esque statement, but on the other hand it gives an explicit distinction between making the definition of a module accessible, and actually spilling that module's contents into your local context.
-
Masklinn says:
2 December 2009
12:40which retains the explicit namespacing in your code
I believe you missed the point of Christopher's post. The point is not that things "stay namespaced" in the code, it's that the imports tell you what appears in your local namespace.
And Ruby doesn't. Case in point with your example: you
require 'digest/md5'
and somehow, you get thisDigest
object/module appearing in the local namespace.At no point did you explicitly mention this
Digest
before you started using it, and afterrequire
there is no mention ofdigest/md5
.By comparison in Python, you'd
import digest
(actuallyhashlib
), and then you'd writedigest.md5.hexdigest("Lorem ipsum")
(actuallydigest.md5("Lorem ipsum").hexdigest()
, but close enough). The symbol youimport
is precisely the one you get in your local namespace, which is what Christopher was trying to flag as the "sensible"(/correct) behavior. And I agree with him.but on the other hand it gives an explicit distinction between making the definition of a module accessible, and actually spilling that module's contents into your local context.
Python does exactly that:
import hashlib
"makes the definition of a module (called hashlib) accessible" whilefrom hashlib import *
dumps all of hashlib's content in the local namespace.And FWIW there is no more automagical resolution of the python module names than there is of the ruby file name.
-
Nicolas Grilly says:
2 December 2009
20:14I completely agree with this. Code is more readable when imports are done right. I'm pleased the CommonJS project has adopted this way of doing imports. Thanks for your post.
-
schmichael says:
2 December 2009
23:31I'm not a PHP developer, but it appears to me as if they may have actually gotten that aspect of namespaces correct:
http://us2.php.net/manual/en/language.namespaces.importing.php
However, accepting PHP's namespacing as "correct" requires retraining your brain to stop seeing backslashes as escape characters. ;-)
-
Asbjørn Ulsberg says:
3 December 2009
15:03I completely love the CommonJS version of "require". It's so intuitive and explicit. You can do somewhat the same in C# as well, with namespace and class aliases;
using MyIO = System.IO;
using C = System.Console;
This is recommended if you have colliding class names in different namespaces or very long class names that could use some abbreviation for brevity.
It's not the default or recommended practice in general, though, so it is indeed less readable unless you know the C# language and .NET framework intimately.
-
data says:
11 January 2010
17:57If you mix up imports with namespaces, then yes, c++ does not support what you ask for. What you really mean is that namespaces done right would mean that you knew from where a function came.
This might be handy, but I don't think there is much benefit between jumping to the imports and using a tool for that job. I prefer staying at the place in the code where I am, so that's why I use cscope in C. Seems more a matter of "I'd like to use grep so make the language syntax fit my usage model" :)