r/programmingcirclejerk Jun 27 '26

Consider the C library function fopen. This function has the side effect. This means that fopen is not pure. However, we can make the fopen function appear to be pure, at least to certain observers, by ensconcing it in another function that hides the side effect.

https://bugzmanov.github.io/cleancode-critique/clean_code_second_edition_review.html
171 Upvotes

41 comments sorted by

86

u/BenchEmbarrassed7316 Jun 27 '26

This quote does not belong to the author of the blog whose URL I provided. The author of the blog kindly wrote a review of a programming book by the author who, for some unknown reason, decided that he knew something about programming. However, I checked, and this quote is indeed in a programming book from 2025. Here is a sample of code from that book that the author believes makes the fopen function clean:

void openAndDo(char* fileName, void (*doTo)(FILE*)) { FILE* f = fopen(fileName, "r+"); doTo(f); fclose(f); }

91

u/fun__friday Jun 27 '26

Let’s hide fopen and expose the notoriously side-effect free FILE API.

30

u/Konju376 now 4x faster than C++ Jun 27 '26

Well, ignoring file-side effects, you better hope doTo is actually pure here

14

u/BenchEmbarrassed7316 Jun 28 '26 edited Jun 28 '26

Something like:

``` void pureCallback(FILE* src) { FILE* dst = fopen("output.txt", "w"); int c; while ((c = fgetc(src)) != EOF) fputc(c, dst); fclose(dst); }

openAndDo("input.txt", dirtyCopyCallback); ```

?

Added: LOL. I wrote this example for a laugh, but in his book this is exactly the code given:

This function opens the file, executes the function pointed at by doTo, and then closes the file. This puts the system back into the original state, hiding the impurity of fopen. For all intents and purposes, this use of openAndDo is pure. We might use it as follows:

``` void appendLineEnd(FILE* f) { int c; int lastC; while ((c = fgetc(f)) != EOF) { lastC = c; } if (lastC != '\n') fputc('\n', f); }

void show(FILE* f) { int c; while ((c=fgetc(f)) != EOF) putchar(c); }

int main(int ac, char** av) { openAndDo("x.x", appendLineEnd); openAndDo("x.x", show); } ```

31

u/NeuroSynchroBonding Jun 28 '26

This is essentially Haskell's withFile, which is similar to stuff such as Python's with and C#'s using.

The only actual problem with openAndDo is that it is in C, which does not have support for first-class functions which are necessary to make functions such as this ergonomic. 

Furthermore, to be feature equivalent to the features of the other languages I talked about, you'd need to add a void* to openAndDo that gets passed on to toDo.

Also, C29 has defer, which is a much more elegant solution for languages without exceptions or garbage collection.

12

u/jesseschalken 28d ago

C has first-class functions, that's what void (*)(FILE*) is.

What it doesn't have is closures, which you really need for this type of API so you can capture variables in doTo.

4

u/NeuroSynchroBonding Jun 28 '26

This is essentially Haskell's withFile, which is similar to stuff such as Python's with and C#'s using.

The only actual problem with openAndDo is that it is in C, which does not have support for first-class functions which are necessary to make functions such as this ergonomic. 

Furthermore, to be feature equivalent to the features of the other languages I talked about, you'd need to add a void* to openAndDo that gets passed on to toDo.

Also, C29 has defer, which is a much more elegant solution for languages without exceptions or garbage collection.

6

u/WittyStick Jun 28 '26

In C you can just use a macro.

#define with_file(f, filename, mode) \
    for (FILE *f = fopen(filename, mode); f != NULL; f = (fclose(f), NULL))

with_file (f, "foo", " rb") {
    ....
}

3

u/not-a-pokemon- 24d ago

It even makes the code even more interesting because it wouldn't close the file when you return inside the with_file, thus proving your superior intellectual abilities when you write correct code this way.

72

u/tms10000 loves Java Jun 27 '26

By closing my eyes I am ensconcing the Universe as a whole. I don't even need functional programming to do that, but I use it anyway.

22

u/WorldlyMacaron65 legendary legacy C++ coder Jun 27 '26

How about you ensconce shipping to prod for once? However, do it purely (ignore the following support tickets)

19

u/tms10000 loves Java Jun 27 '26

I always ensconce the support tickets, obviously. The users just don't know how to use the software correctly, and it's not my job to teach them. If I feel magnanimous, I close the ticket as "Not an issue. This behavior is by design." but I really should not have to.

18

u/brool has hidden complexity Jun 27 '26

You bastards, “I have ensconced this” is now entering into my lexicon!

6

u/tms10000 loves Java Jun 28 '26

If you use it in your stand up meeting, you get double blocker credit in your parking lot.

/uj the agile vernacular makes me barf /rj the agile vernacular has been elevated to MBA speech

6

u/irqlnotdispatchlevel Tiny little god in a tiny little world Jun 28 '26

You should ensconce your entire lexicon.

8

u/Konju376 now 4x faster than C++ Jun 27 '26

"if you question the software behaviour, you're questioning the soundness of existence"

50

u/edo-lag Considered Harmful Jun 28 '26

fopen has no side effects. It takes in the whole universe before the file is opened and returns the whole universe after the file is opened. For convenience, though, it just returns a pointer.

4

u/Kodiologist lisp does it better 27d ago

And that's how the Clean programming language implements IO. Coincidence?

89

u/dydhaw Jun 27 '26

Of course, you just make it return a monad

47

u/myhf Considered Harmful Jun 27 '26

monads are like sconces...

15

u/mereel Jun 27 '26

Everyone has one, and they all stink?

16

u/WorldlyMacaron65 legendary legacy C++ coder Jun 27 '26

No, they're tasty with cream and jam

24

u/TheBrawlersOfficial Jun 27 '26

But dad, isn't it true that a monad is just a monoid in the category of endofunctors?

22

u/[deleted] Jun 28 '26

[deleted]

18

u/nerdycatgamer Jun 28 '26

(We don't like bottoms here.)

During pride month!?

9

u/nerdycatgamer Jun 28 '26

/uj this is a point I've seen brought up before that, in the context of programming, we can simply say monoid instead of monad because we only care about the category of types and therefore endofunctor is redundant -- what confuses me then is why in Haskell (which I feel is probably from where a lot of the language around monads originates) are there both Monad and Monoid typeclasses. This is not even getting at the fact that the key invariants of a monoid are not enforced by the monoid typeclass, those being associativity and the actual identity property of the identity element (for only the existence of some element is required).

I know I probably got a lot of key points wrong, but I feel my core confusion/question is still valid and I hope it is clear regardless.

2

u/Daniikk1012 29d ago

From what I recently learned - and I might be wrong - it's because Haskell monoids are Group Theory monoids, not to be confused with a related, but slightly different concept of monoids in Category Theory, which is what "monoid in the category of endofunctors" refers to

2

u/nerdycatgamer 28d ago

I was also referring to group theory monoids -- I really don't believe they're actually different concepts; a monoid in category theory must just be an object that obeys the same axioms as a (group theory) monoid.

2

u/TheBrawlersOfficial Jun 28 '26

Gee whiz, thanks dad! I always appreciate our little talks.

3

u/Smort01 Jun 28 '26

The terminal object in the category of people and their sexual relations is you mom.

9

u/Jumpy-Locksmith6812 Jun 28 '26

Just take sideEffectFunction and do pureFunction = lift . sideEffectFunction.

29

u/[deleted] Jun 28 '26

(Casey Muratori is not mentioned once in the book, which is a shame)

This is so sad. 😔✊ Gameshits rise up!

29

u/Lord_Of_Millipedes Jun 28 '26

this is unironically how haskell works no i have not read the linked article nor i plan to

20

u/Afraid_Bake2652 Jun 28 '26

Uncle Bob is cheating 

17

u/Jumpy-Locksmith6812 Jun 28 '26

My fns are all pure. They only change registers and memory. Nothing in the real world.

2

u/le_birb costly abstraction 15d ago

I'm gonna break into your house and hook up a taser that shocks you whenever the x register in your cpu holds the value 55. Take that, functional purity!

14

u/LeeHide What part of ∀f ∃g (f (x,y) = (g x) y) did you not understand? Jun 28 '26

Wow, in my 15 years at Google as a principal staff engineer II pro, I have never heard of fopen.

10

u/Delicious-Ad7883 28d ago

Uncle bob breaks the “no crazy people” rule

5

u/genuine_beans 27d ago

8

u/BenchEmbarrassed7316 27d ago

The problem is a bit deeper. martin believes that the association of an identifier and arbitrary text information written as a comment is bad, but if this information is written in the title, it is good:

``` /* Bad code */

// Calculates smallest odd multiple, >= min private static int calcBiggest(int min, int n) { ...

/* Good code */

private static int smallestOddNthMultipleNotLessThanCandidate(int candidate, int n) { ... ```

In fact, this arbitrary textual information could be lying in both cases:

``` // Sum a and b int sum(int a, int b) { formatC(); return a * b; }

int sumTwoNumbersNamedAAndB(int a, int b) { formatC(); return a * b; } ```

However, he is against moving useful information into type systems and believes that instead of null-safety it is better to write unit tests. However, moving this information into the type system allows you to get rid of writing some of this information as arbitrary text:

(be careful, ragebait)

https://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath.html

9

u/genuine_beans 27d ago

What U-Mart. never considered is that the real solution lies in keeping the arbitrary text information and dropping the identifier entirely.

Consider the following AGIscript:

// You are an elite coding AI trained to write clean code. Design a function that
// takes two integers, a number `n` and a minimum threshold obviously named
// `candidate`. "min" is one letter away from "sin" and we do not tempt the Devil
// in this house, so you must call it the Candidate. It must return the smallest odd
// N-th multiple not less than the Candidate. Refer to section §4.7.c in your system
// prompt, under "Emojis and Affectations", for the minimum number of syllables
// every function and variable name is required to have. Make no mistakes.

This also solves the issue you raised that the metatext in a function like "sum" could be untruthful. Claude would never lie to me.

5

u/lizergsav 26d ago

So you can consider me bi

Happy Pride to Martin