Package-local nicknames
Got around to reading this blog post, seems like something to ramble about myself as ultimately I disagree though I like his arguments. I will now proceed to jump into the shark pool, but while in the air let me babble--First, I think if you're using the package-inferred-system style, then yes, nicknames are largely pointless. (Though not package prefixes, which I'll get to in a bit.) Most of the time it'll be fine to just :use the packages you need for that file. While in that file, you know all the symbols in use are defined at the top of the file if you're ever in doubt where they come from and don't feel like describing/hovering over the symbol or jumping to the source. Like Java, using this style also encourages to keep files relatively smaller as well, which helps keep the number of symbols at play per file relatively smaller.
I'm not a huge fan of package-inferred-system, at least in the way ADSF implemented it. Also, it seems like the linked post's main complaint isn't so much around nicknames, but around package names entirely. Their example Python code of preferring from sys import argv, exit over import sys and later using sys.argv and sys.exit suggests that to me.
I prefer sys.exit anyway, because I know, reading any code anywhere, if I see sys.exit I not only know exactly where it's defined, I know exactly what it does. Most programmers don't read most code top-to-bottom, especially in large projects, but in small contextual chunks. If I don't have to wonder about the nature of a naked exit, even if resolving my wonder is pretty fast (describe symbol..), I'd prefer to not have to.
But this is an argument in favor of package prefixes generally, not nicknames. I think it's dumb if someone does something like import sys as s and uses s.exit. However if it was import com.example.foo.bar.sys as sys, I'm actually happy, and would tend do that too depending on the symbols inside sys I actually care about.
So ultimately, my argument in favor of having package-local nicknames as a sometimes-useful feature, is when a package's prefix is too annoying to use directly, and the nickname chosen is sensible and maybe should have been provided by the package owner themselves (despite the global nickname collisions issue). With the power of tab-complete, this isn't that often.
To get more into using package prefixes themselves, even if nicknamed to a shorter but reasonable version, I think it actually helps readability (know exactly what symbol is being referenced) and helps development speed. The blog suggests putting in the effort to craft the perfect namespace... ok, maybe it's worth it, but you can only do that near the end once you're reasonably sure how this piece of code is going to be constructed (having written most of it already). Is this "polish" step worth it? I don't think so, in general, and again can hurt readability as much as help it depending. In Python, I can see an argument for both something like import pygame.sprite as sprite and using things from it like sprite.Group/sprite.OrderedUpdates or sprite.Sprite or just bring in everything you need from sprite and use Group and Sprite directly. They're both pretty readable.
But, like Java, the story doesn't end there. In both of those language's version of OOP, methods are coupled with classes, and so classes themselves act as another layer of namespacing. You can of course define things to refer to individual method names without any prefix (import static in Java) but those circumstances are rather rare. Most of the time, you'll be using the foo.bar syntax or MyFoo.bar for a class method.
This extra namespace is actually rather helpful, both for readability and for development ease. When I press my auto-complete shortcut after typing a symbol and dot, I get a list of things only relevant to that symbol's type. If I'm still in development mode, this is great. If I'm in reading mode, this is also nice, as I know without having to do anything else that this method being called is related to this object and not something else in this file.
In Lisp, there's no way to get this extra namespace, packages are all we've got. I've found it helpful to get close by structuring some OOP code in terms of having a package for a class (or classes if they are very related, like a Sprite and Group of sprites are) and the methods involved with them. It can get a bit clunky sometimes, and those are the times I think importing/using the symbols is the right choice, but most of the time, and especially the time of first development when you're not even sure what all symbols you'll actually end up needing to use in the end, the package name acts as a way to filter tab-completing a symbol that's relevant to an object, and later on is explicit and readable for what this function call or whatever is doing. Thus, you might have a sprite package and while it could be considered somewhat ugly to type (sprite:update enemy) (or worse, (sprite:update sprite) over (update enemy), it's often worth it in the end coming back to the code months later and while writing the code not having to break your train of thought to go update a stupid package definition form somewhere. (If any CL editors had nice auto-import-symbol and auto-export-symbol shortcuts that update the relevant source code, like is common in Java, I might change my opinion on this somewhat.)
So to attempt to sum up: if what you're importing is a simple function or simple variable, fine, probably more often better to just do that rather than use package prefixes or nicknaming them. If what you're importing instead is more like a protocol, or even a data structure, keeping the package prefix is better and nicknaming it to a shorter symbol can sometimes be tasteful.
Posted on 2023-05-19 by Jach
Tags: lisp, programming
Permalink: https://www.thejach.com/view/id/413
Trackback URL: https://www.thejach.com/view/2023/5/package-local_nicknames
Recent Posts
2024-10-04
2024-09-25
2024-09-23
2024-09-22
2024-09-02