Skip to content

Instantly share code, notes, and snippets.

@kasajian
Last active October 9, 2024 16:58
Show Gist options
  • Save kasajian/2c34b01a24036ee61efdeea97a13f0be to your computer and use it in GitHub Desktop.
Save kasajian/2c34b01a24036ee61efdeea97a13f0be to your computer and use it in GitHub Desktop.
Logging and are Pure Functions necessary for FC-IS (Functional Core and Imperative Shell)

Logging and are Pure Functions necessary for FC-IS (Functional Core and Imperative Shell)

One could say a pure function has the following characteristics:

  • Deterministic: The same input values always produce the same output values
  • No side effects: The function does not change any variables or objects that existed before it was called
  • No external dependencies: The function does not read or write data from outside sources, such as files, databases, web services, or the console

And this would not be incorrect. However, rather than applying these criteria to a function in order to determine its purity, I think it's better to ask yourself and understand why is it important, and is it?

For example, let's say you'r designing your code to follow the "FC-IS" (Functional Core and Imperative Shell) pattern. Do you care if the functions in the functional-Core part are all pure, or that they are merely referential transparency within the execution context?

what if the function reads from a file? If you're immediate reaction is "that's an external dependency, so not pure. But for a few minutes, let's see why / if this matters.

Let's say the functional-Core part of my code has a queue, and internally it has a hard-coded internal capacity set to 32 items. Now, what I want to do is, make that into a constant and put it into a source file where I keep all my #defines, consts, etc. When the code compiles, the "32" is embedded in the function, so the binary is no different than before. It's still pure. Some say no because I am using data outsdie of the function. I say it's pure becausee that data isn't changing.

What if I put the constants in a .json file, and then during the build, convert the .json to a source file, which gets compiled into the binary? When the code compiles, the "32" is still embedded in the function, so it's still pure. Again, some say no because I am using data outsdie of the function. But I still say it's pure becausee that data isn't changing.

What if at the top of the main() entry point to the application, I read the .json file, and initialize global variables which are then frozen, so they cannot be changed during the lifetime of the app.

Now the binary is different. However, when the function is called, although it's relying on external data, a global, technically it's not pure. But to me, it is. It's not changing during the lifetime of the application, at least after the initial "preamble".

We're really breaking the rules now, but let's keep going.

In the FC-IS world, code execution usually alternates between the imperitive part and the functional part, back and forth. Every time we enter the functional part, we are in a brand new universe, no memory of the last time we were there. Once we exit the functional part, that universe ends forever. As long as the global data that the functional part doesn't change, it's no different to relying on hard-coded data embedded within the functions.

So what am I saying here? How can a function be pure if, by definition it cannot rely on data outside of the function, yet it does.

Back to reality: By the strict definition of function purity, it's not pure.

But for implementing a Functional-Core, I'm saying purity may not necessarily be what you're looking for.

I demonstrated a perfectly reasonalbe implementation that compiles with the essense of Functional-Core where a function does not comply with the strict definition of purity.

The reason this is okay is because in the context of the FC-IS pattern, the routines within the Functional-Core portion need not be pure, but merely "referentially transparent".

A function (or expression) is referentially transparent if it can be replaced with its corresponding value without changing the program's behavior.

But a pure function has to be both referencial transparency and be free of side effects.

I heavily rely on logging to diagnose my code. At times, I may need to instrument my code at the lowest possible level to see what's going on. This means I may have to add log statements inside of a "pure" function. In doing so, I've introduced a side-effect, which means the function is no longer pure.

Some may say, you should pass in a logging monad and collect the log statements in the Functional-Core portion, and do the actual logging only when you're on the outside in the impertive shell. You could, and sometimes you should. But sometimes that's not only inconvenient, it's nearly impossible.

Logging inside of a pure function doesn't break any of the assumptions I am relying on when architecting an FC-IS application. Although it makes the function no longer pure, I don't care because the function is still referentially transparent, which I do care about.

References:

Anders: https://www.aarthiandsriram.com/p/our-dream-conversation-anders-hejlsberg

Quotes:

  • "It turns out that this notion of programming with islands of functional purity is actually really a useful way to think about a lot of problems"
  • "This notion that you try to park the side effects in a place where you can reason about them, and then you have whole portions of code where you just know, well, there's no mutability over here, so that can just be parallelized and I'll have to think about it, because no one ever modifies this data structure. so I can have as many threads banging on it as I want and nothing's going to go wrong. It's empowering."
  • "You can have mutability or you can have parallelism, but you can't have both together. No one understands what happens when you have both together. That's why functional programming is great; you can do parallelism because there are no side effects. Or you can do mutability."

Gary Bernhardt:

Boundaries

https://www.destroyallsoftware.com/talks/boundaries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment