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.
 | 
			
		||||
        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"]
 | 
			
		||||
  id = "01K05F3E3DN1PY9ZWN98ZE5HVV"
 | 
			
		||||
- ### [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"
 | 
			
		||||
    - I recently rebuilt the treehouse to use a virtual file system for its source and target directories.
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    % id = "01JDJJSEWA7K5T3Z0Y6NQ8RBGX"
 | 
			
		||||
    - 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"
 | 
			
		||||
    - I'm an adorable little cat boy. purrow! _snuggle snuggle_
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    % id = "01JDTBGSJ7RVYR0VD693FN10QH"
 | 
			
		||||
    - 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"
 | 
			
		||||
    - 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"
 | 
			
		||||
        - 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"
 | 
			
		||||
    - sidebars! also known as, _"enjoying the main content? how about I distract you from it so that you can't focus!"_
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        % id = "01HR9ZTS8RQ1EN0THYEVNQRY2A"
 | 
			
		||||
        - seriously though. I don't like them.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue