new post: header files are cool, actually
This commit is contained in:
parent
122f611d6d
commit
d8d3ebdd38
2 changed files with 178 additions and 4 deletions
167
content/h.dj
Normal file
167
content/h.dj
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
title = "header files are cool, actually"
|
||||||
|
|
||||||
|
+++
|
||||||
|
|
||||||
|
You know what.
|
||||||
|
I actually kinda like header files.
|
||||||
|
|
||||||
|
Yeah, _those_ things.
|
||||||
|
From C and C++.
|
||||||
|
|
||||||
|
I've heard lots of opinions from fellow programmers, saying it's annoying to have to update your signatures in _two_ places.
|
||||||
|
However, I believe a lot of the extra repetition comes from _the bad design of C++'s classes_, rather than the idea of header files itself.
|
||||||
|
|
||||||
|
I've mentioned this briefly before in [C++ without Classes][page:programming/cxx-without-classes], but a lot of redundant editing is born out of C++'s poor implementation of class encapsulation.
|
||||||
|
In C++, your implementation details come out leaking into the header file.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// .h file
|
||||||
|
|
||||||
|
class World
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Entity& entity(Entity_Id id);
|
||||||
|
|
||||||
|
void update(const Update_Context& cx);
|
||||||
|
void draw(const Draw_Context& cx);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void draw_entities(const Draw_Context& cx);
|
||||||
|
|
||||||
|
Entity entities[256];
|
||||||
|
// etc
|
||||||
|
};
|
||||||
|
|
||||||
|
// .cpp file
|
||||||
|
|
||||||
|
Entity& World::entity(Entity_Id id) { /* ... */ }
|
||||||
|
|
||||||
|
void World::update(const Update_Context& cx) { /* ... */ }
|
||||||
|
|
||||||
|
void World::draw(const Draw_Context& cx) { /* ... */ }
|
||||||
|
|
||||||
|
void World::draw_entities(const Draw_Context& cx) { /* ... */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
Closing yourself in the bubble of C++ might make you think header files are just that.
|
||||||
|
Annoying nuisances, artifacts of language design of the past.
|
||||||
|
DRY killers.
|
||||||
|
|
||||||
|
Hacks of the dated C compilation model.
|
||||||
|
|
||||||
|
However, if you observe what header files (or their equivalents) look like in _other_ languages---you start seeing something different.\
|
||||||
|
Consider the same example, but written in C.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
Entity entities[256];
|
||||||
|
// etc
|
||||||
|
} World;
|
||||||
|
|
||||||
|
Entity* world_entity(World* w, Entity_Id id);
|
||||||
|
|
||||||
|
void world_update(World* w, const Update_Context* cx);
|
||||||
|
void world_draw(const World* w, const Update_Context* cx);
|
||||||
|
```
|
||||||
|
|
||||||
|
See the difference? `draw_entities` is nowhere to be seen.
|
||||||
|
Meanwhile, in the corresponding implementation file:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Entity* world_entity(World* w, Entity_Id id) { /* ... */ }
|
||||||
|
|
||||||
|
void world_update(World* w, const Update_Context* cx) { /* ... */ }
|
||||||
|
|
||||||
|
static void draw_entities(const World* w, const Update_Context* cx) { /* ... */ }
|
||||||
|
|
||||||
|
void world_draw(const World* w, const Update_Context* cx) { /* ... */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
The `draw_entities` implementation detail---a _private function_---is still very much there!
|
||||||
|
|
||||||
|
In OCaml, you would do something similar.
|
||||||
|
Here's `world.mli`:
|
||||||
|
|
||||||
|
```ocaml
|
||||||
|
type t
|
||||||
|
|
||||||
|
val entity : t -> Entity.id -> Entity.t
|
||||||
|
val update : t -> Game_loop.update_context -> unit
|
||||||
|
val draw : t -> Game_loop.draw_context -> unit
|
||||||
|
```
|
||||||
|
|
||||||
|
And here's `world.ml`:
|
||||||
|
|
||||||
|
```ocaml
|
||||||
|
type data = {
|
||||||
|
entities: Entity.t array;
|
||||||
|
}
|
||||||
|
type t = data ref
|
||||||
|
|
||||||
|
let entity w id = (* ... *)
|
||||||
|
|
||||||
|
let update w cx = (* ... *)
|
||||||
|
|
||||||
|
let draw_entities w cx = (* ... *)
|
||||||
|
|
||||||
|
let draw w cx = (* ... *)
|
||||||
|
```
|
||||||
|
|
||||||
|
(Disclaimer: my knowledge of OCaml is pretty surface level. This is probably not valid syntax.)
|
||||||
|
|
||||||
|
Our implementation detail `draw_entities` appears again, but only in the module's interface file.
|
||||||
|
|
||||||
|
And that's how I view header files: as _public interface declarations_.
|
||||||
|
They say, "this is the set of public procedures you can run on these types."
|
||||||
|
|
||||||
|
When viewed this way, header files become extremely useful for tracking changes in a module's public API, semantic versioning-wise.
|
||||||
|
|
||||||
|
But perhaps more importantly, because headers written with the "public interface" philosophy in mind are so laser-focused on exposing the... well, _public interface_, they become your module's _reference documentation._
|
||||||
|
|
||||||
|
My favourite example of this is [Dear ImGui](https://github.com/ocornut/imgui/blob/v1.92.1/imgui.h), which takes this idea to the extreme.
|
||||||
|
Dear ImGui needs no documentation generator like Doxygen.
|
||||||
|
It's all right there in the header file, and all you have to do is open it in your text editor and grep for whatever you need at the moment.
|
||||||
|
|
||||||
|
Here's an excerpt of `imgui.h` (reformatted to fit my blog's column limit better):
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Windows
|
||||||
|
// - Begin() = push window to the stack and start appending to it.
|
||||||
|
// End() = pop window from the stack.
|
||||||
|
// - Passing 'bool* p_open != NULL' shows a window-closing widget in
|
||||||
|
// the upper-right corner of the window, which clicking will set
|
||||||
|
// the boolean to false when clicked.
|
||||||
|
// - You may append multiple times to the same window during
|
||||||
|
// the same frame by calling Begin()/End() pairs multiple times.
|
||||||
|
// Some information such as 'flags' or 'p_open' will only be
|
||||||
|
// considered by the first call to Begin().
|
||||||
|
// - Begin() return false to indicate the window is collapsed or fully
|
||||||
|
// clipped, so you may early out and omit submitting anything to
|
||||||
|
// the window. Always call a matching End() for each Begin() call,
|
||||||
|
// regardless of its return value!
|
||||||
|
// [Important: due to legacy reason, Begin/End and BeginChild/EndChild
|
||||||
|
// are inconsistent with all other functions such as BeginMenu/EndMenu,
|
||||||
|
// BeginPopup/EndPopup, etc. where the EndXXX call should only be
|
||||||
|
// called if the corresponding BeginXXX function returned true.
|
||||||
|
// Begin and BeginChild are the only odd ones out.
|
||||||
|
// Will be fixed in a future update.]
|
||||||
|
// - Note that the bottom of window stack always contains a window
|
||||||
|
// called "Debug".
|
||||||
|
IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0);
|
||||||
|
IMGUI_API void End();
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare this to grepping through an `.rs` file, which usually yields lots of unrelated results.
|
||||||
|
I pretty much never grep through Rust files to look at APIs, while I do it all the time in C++.
|
||||||
|
|
||||||
|
Perhaps it's a sign that the complexity of JavaScript-heavy online HTML documentation viewers with mediocre search functionality is entirely self-inflicted.\
|
||||||
|
(I'll give them though that rich text is pretty cool, and Hackage's global type signature search is _wicked_ cool. Nothing that a sufficiently advanced LSP server shouldn't be capable of doing, though.)
|
||||||
|
|
||||||
|
(...and also, fuck Doxygen. I hate using Doxygen so much. But I'll leave that for another time.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Either way, that does it for The Ramblings I Will Link Whenever Someone Mentions Header Files Are The Bane Of Their Existence.
|
||||||
|
|
||||||
|
Thank you for reading.
|
|
@ -26,6 +26,13 @@ if you've been wondering what I've been up to, you've come to the right place.
|
||||||
if you want to read any of the posts, follow the links.
|
if you want to read any of the posts, follow the links.
|
||||||
it's like that by design.
|
it's like that by design.
|
||||||
|
|
||||||
|
% tags = ["programming", "cxx"]
|
||||||
|
id = "01K1Y3G5N1KGCN1E9B36QTYMSZ"
|
||||||
|
- ### [header files are cool, actually][page:h.dj]
|
||||||
|
|
||||||
|
% id = "01K1Y3G5N1WTEYF5X4JJHGR5XN"
|
||||||
|
- in which I ramble about how to write good header files.
|
||||||
|
|
||||||
% tags = ["meow", "shower"]
|
% tags = ["meow", "shower"]
|
||||||
id = "01K05F3E3DN1PY9ZWN98ZE5HVV"
|
id = "01K05F3E3DN1PY9ZWN98ZE5HVV"
|
||||||
- ### [furry! ---w--- (version 2)][page:furry.dj]
|
- ### [furry! ---w--- (version 2)][page:furry.dj]
|
||||||
|
@ -201,7 +208,7 @@ if you've been wondering what I've been up to, you've come to the right place.
|
||||||
|
|
||||||
% id = "01JDJJSEWAVZGJN3PWY94SJMXT"
|
% id = "01JDJJSEWAVZGJN3PWY94SJMXT"
|
||||||
- I recently rebuilt the treehouse to use a virtual file system for its source and target directories.
|
- I recently rebuilt the treehouse to use a virtual file system for its source and target directories.
|
||||||
|
|
||||||
% id = "01JDJJSEWA7K5T3Z0Y6NQ8RBGX"
|
% id = "01JDJJSEWA7K5T3Z0Y6NQ8RBGX"
|
||||||
- this is an exploration of how I built my abstraction, how it works, and what I learned from it.
|
- this is an exploration of how I built my abstraction, how it works, and what I learned from it.
|
||||||
|
|
||||||
|
@ -232,7 +239,7 @@ if you've been wondering what I've been up to, you've come to the right place.
|
||||||
|
|
||||||
% id = "01JDTBGSJ7KB9GYWQZ8G9D97NY"
|
% id = "01JDTBGSJ7KB9GYWQZ8G9D97NY"
|
||||||
- I'm an adorable little cat boy. purrow! _snuggle snuggle_
|
- I'm an adorable little cat boy. purrow! _snuggle snuggle_
|
||||||
|
|
||||||
% id = "01JDTBGSJ7RVYR0VD693FN10QH"
|
% id = "01JDTBGSJ7RVYR0VD693FN10QH"
|
||||||
- a lesson in Shock Therapy & Getting Over It {-with Bennett Foddy-}
|
- a lesson in Shock Therapy & Getting Over It {-with Bennett Foddy-}
|
||||||
|
|
||||||
|
@ -332,7 +339,7 @@ if you've been wondering what I've been up to, you've come to the right place.
|
||||||
|
|
||||||
% id = "01J293BFEB4G7214N20SZA8V7W"
|
% id = "01J293BFEB4G7214N20SZA8V7W"
|
||||||
- sometimes people call me crazy for saying that bashing JavaScript is senseless and that it's not as bad of a language as people make it out to be.
|
- sometimes people call me crazy for saying that bashing JavaScript is senseless and that it's not as bad of a language as people make it out to be.
|
||||||
|
|
||||||
% id = "01J293BFEBYSW4K7YHVN42J3WP"
|
% id = "01J293BFEBYSW4K7YHVN42J3WP"
|
||||||
- so I decided to collect my thoughts into a nice little page I can link easily.
|
- so I decided to collect my thoughts into a nice little page I can link easily.
|
||||||
|
|
||||||
|
@ -409,7 +416,7 @@ if you've been wondering what I've been up to, you've come to the right place.
|
||||||
|
|
||||||
% id = "01HR9ZTS8RY3N4EJM5W7WBTF0G"
|
% id = "01HR9ZTS8RY3N4EJM5W7WBTF0G"
|
||||||
- sidebars! also known as, _"enjoying the main content? how about I distract you from it so that you can't focus!"_
|
- sidebars! also known as, _"enjoying the main content? how about I distract you from it so that you can't focus!"_
|
||||||
|
|
||||||
% id = "01HR9ZTS8RQ1EN0THYEVNQRY2A"
|
% id = "01HR9ZTS8RQ1EN0THYEVNQRY2A"
|
||||||
- seriously though. I don't like them.
|
- seriously though. I don't like them.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue