On Friday, 18 March 2022 at 18:16:51 UTC, Ali Çehreli wrote:
As a long-time part of the D community, I am ashamed to admit
that I don't use dub. I am ashamed because there is no
particular reason, or my reasons may not be rational.
dub is legitimately awful. I only use it when forced to, and even
making my libs available for others to use through it is quite an
unnecessary hassle due to its bad design.
That sounds great but aren't there common needs of those
modules to share code from common modules?
Some. My policy is:
1) Factor out shared things when I *must*, not just because I can.
So if it just coincidentally happens to be the same code, I'd
actually rather copy/paste it than import it. Having a private
copy can be a hassle - if a bug fix applies to both, I need to
copy/paste it again - but it also has two major benefits: it
keeps the build simple and it keeps the private implementation
actually private. This means I'm not tempted to complicate the
interface to support two slightly different use cases if the need
arises; I have the freedom to edit it to customize for one use
without worrying about breaking it for another.
When I must factor out it is usually because it is part of a
shared public *interface* rather than an implementation detail. A
shared interface happens when interoperability is required. The
biggest example in my libs is the Color and MemoryImage objects,
which are loaded from independent image format modules and then
can be loaded into independent screen drawing or editing modules.
Loading an image then being unable to display it without a type
conversion* would be a bit silly, hence the shared type.
* Of course, sometimes you convert anyway. With .tupleof or
getAsBytes or something, you can do agnostic conversions, but it
is sometimes nice to just have `class SpecialImage {
this(GenericImage) { } }` to do the conversions and that's where
a shared third module comes in, so they can both `import
genericimage;`.
2) Once I do decide to share something, there's a policy of tiers:
first tier has zero imports (exceptions made for druntime and
SOMETIMES phobos, but i've been strict about phobos lately too).
These try to be the majority of them, providing interop
components and some encapsulated basic functionality. They can
import other things but only if the user actually uses it. For
example, dom.d has zero imports for basic functions. But if you
ask it to load a non-utf8 file, or a file from the web, it will
import arsd.characterencodings and/or arsd.http2 on-demand.
Basic functionality must just work, it allows those opt-in
extensions though.
second tier has generally just one import, and it must be from
the first tier or maybe a common C library. I make some
exceptions to add an interop interface module too, but I really
try to keep it to just one. These built on the interop components
to provide some advanced functionality. This is where my
`script.d` comes in, for example, extending `jsvar.d`'s basic
functionality with a dynamic script capability.
I also consider `minigui.d` to be here, since it extends
simpledisplay's basic drawing with a higher-level representation
of widgets and controls, though since simpledisplay itself
imports color.d now (it didn't when I first wrote them... making
that change was something I really didn't want to do, but was
forced to by practical considerations), minigui does have two
imports... but still, I'm leaving it there.
Then, finally, there's the third tier, which I call the
application/framework tier, which is the rarest one in my
released code (but most common in my proprietary code, where I
just `dmd -i` it and use whatever is convenient). At this point,
I'll actually pull in whatever I want (from the arsd package,
that is) so there is no limit on the number of imports. I still
tend to minimize them, but won't take extraordinary effort. This
is quite rare for me to do in a library module since this locks
it out of use by any other library module! Obviously, no tier one
or two files can import a tier three, so if I want to actually
reuse anything in there, it must be factored out back to
independence first.
C libraries btw are themselves also imports, so I also minimize
them, but there's again some grey area: postgres.d use both
database.d as the shared interface, but libpq as its
implementation. I still consider it tier two though, despite a C
library being even harder for the user to set up than 50 arsd
modules.
3) I try to minimize and batch breaking changes, including breaks
to the build instructions. When I changed simpledisplay to import
color, it kinda bugged me since for a few years at that point, I
told people they can just download it off my website and go.
I AM considering changing this policy slightly and moving more to
tier two, so it is the majority instead of tier one. All my new
instructions say "dmd -i" instead of "download the file" but I
still am really iffy on if it is worth it. Merging the Uri
structs and the event loops sounds nice, having the containers
and exception helpers factored out would bring some real
benefits, but I've had this zero-or-one import policy for so
long, making it one-or-two seems too far. But I'll decide next
year when the next breaking change release is scheduled.
(btw my last breaking change release was last summer and it
actually broke almost nothing since i was able to migration path
it to considerable joy. im guessing most my users never even
noticed it happened)
Despite such risks many projects just pull in code. (?) What am
I missing?
It is amazing how many pretty silly things are accepted as gospel.