the jenny jam blog

Big Central Header Files

I was thinking about a bit of a design thing I've notice in certain pedagogical and intentionally small codebases have -- often applications and not libraries -- of having a central file of like foo.h, which contains all of the internal implementation details, usually with each logical sub-system in a sub-area. Each of these logical subsections can have it's own implementation file which can contain internal helper files or internal type definitions that are really not necessarily to leak -- everything in the header file is the API for every unit (but if you're doing this you probably don't care that much about modularity).

Header files are sort of kind of nice as an index?

Wait stop come back.

I think it's generally considered that using textual inclusion and semi-global states for our module system is unideal and that this is a historical quirk of C, it's also sort of nice because the definitions and general summary of a codebase can sometimes be placed in a core place.

In most languages, function declarations can't have standalone prototypes -- this is reasonable language design, but also means you don't have a natural sort of index and listing location.

If you look at something that has similar tiny scope type stuff, bottle.py has this dynamic where you can't eyeball the general layout of everything in one spot, you have to jump around. But otherwise it kind of has similar stuff going on in terms of breaking it up into sections?

Some examples

I will probably add more here as I can think of them

A throwaway example

I think a general example might be e.g.

/** @file interp.h */

/// various utilities & portability stuff
/// util.c

void dump_hex(const void* data, size_t datlen);

/// representation of values
/// value.c
typedef struct {...} Value;
typedef struct {...} Func;
typedef struct {...} CallNode;

/// parsing values into nodes
/// parser.h
typedef struct {...} AstNode;
typedef struct {...} CallNode;
typedef struct {...} BranchNode;
typedef struct {...} AssignNode;

void parse(...); /// whatever

/// Environment & scope management
/// env.h
typedef struct {...} Env;
Env* newEnv();
void envSet(const char key[], Value value);
bool envGet(const char key[], Value* out_res);
Env* extend();

/// garbage collection
/// gc.c
void gc();

/// interpretation and AST walking
void Status eval(Astnode* node, Value* out);
void Status apply(Func* func, int argc, Value args[], Value* out);

/// main.c
int main(int argc, char argv[][]);

single file libs

These aren't really going for the same thing, the but Single File Libs sort of look at stuff trying to have a certain small scale legibility?

Isn't this anti-modularity?

I mean kinda!

Anytime you change any part of the API in the header you're gonna have to recompile every object file. For a lot of projects that's definitely not worth it, but for smaller scale stuff, I think it's fine, and it sorta of makes it easy to get a full...scale?