LmCast :: Stay tuned in

Function Arguments Are Not Function Colors

Recorded: Sept. 8, 2026, 8 p.m.

Original Summarized

Why Function Arguments Are Not Function Colors - iRi

iRi
Celebrating Over 10 Years Of Being Too Lazy To Pick A Tagline

Why Function Arguments Are Not Function Colors

2026-09-08
Programming

Page content

Color As A Change Dependency Graph Shape
Change Isolation
Color Definition
Advanced Topic: Partial Coloring

In several debates online about function
colors
over the years, people have argued the apparently reasonable position
that function arguments can constitute colors as well.
For instance,
in Go, there is a context.Context
value, which manages timeouts,
cancellation, and a small amount of data that can be threaded through
various functions. It is, by design, intended to be something passed
through functions even if the receiver doesn’t use it directly but
only passes it along. Generally, once one function starts using a
context, you should thread it along to all called functions that could
conceivably have a use for it.
So, superficially, this appears to be a color. Once you have this
particular argument in hand, you “have” to call all future functions
of that type with that argument.
Extending this out you can end up arguing that all function parameters
are “colors”, as some do.
A quick, though still important, objection is to observe that if
this was the case, we wouldn’t be talking about this at all. If
async was merely one “color” in a dazzling chaotic rainbow of thousands
of other colors, this “color” concept wouldn’t have been invented 50+
years after the invention of programming language functions. There
is still something distinct about how async works in some languages
that is worth trying to capture and understand.
But understanding the situation deeply from one example is difficult.
Here is my attempt to turn it into a criterion rather than a single example:
Color As A Change Dependency Graph Shape
Let’s phrase the problem this way:
Suppose I have to change an attribute of some function 4 levels buried
in a call stack, be that a parameter that it takes, whether it is
“async” or not, or some other thing like “whether or not it has ASM in
it” or any other attribute of the function. Any change to the function
at all, depending on what characteristics the local language permits functions
to have.
This change could result in having to change the callers as well.
There’s three basic cases that can emerge:

No change to any caller is necessary.

The change may require a change to the direct caller.

The change may require a change to all functions in the stack.

Graphically, we have the following. For the no-color, function
parameter case, the “need to change” relationship may look like this:

For the color case, it looks like this:

The difference between a “color” and a “normal” change is that in the
case of the color, the change to the
bottom function in the stack does not just affect its parent. It
immediately and inescapably affects every function above it in the
call stack, and therefore, they all must react to the change.
For a “normal” change, it only propagates to the caller. It may
then chain to its caller. If it propagates all the way up to the
top-level function, as demonstrated in the diagram, this may
seem like a distinction without a difference.
But the key is that the diagram isn’t showing the common case, it is
showing the worst case. In practice, functions almost always
break the chain before it reaches to the top of the call stack. Any
function can “catch” the propagating change and encapsulate it
so that functions above it won’t see it anymore.
You can measure this by how often you do in fact end up threading a
change up the stack… well… you could back in the Bad Old Days when
we were programming by hand. You know it’s something you do, but it’s
certainly not something you do all the time, and it’s not something you
often did all the way up to the main function at the top.
Change Isolation
We humans pervasively use structured
programming precisely because of its ability to do this for most function
changes, to isolate higher functions from needing to know every detail
of the lower functions.
What would it even look like for the top-most
function to have to know “everything” that all the child functions end
up ever needing? For every function parameter change in the entire
program to somehow force a change all the way up the entire call
stack?
Clearly this does not happen. It so thoroughly does not happen that
you may be having difficulty even imagining what it is I am talking
about.
For instance,
in the case of Go’s context, which I keep bringing up not just because
Go is one of my main programming languages but because this is the
example that keeps coming up, any function anywhere can call
context.Background() to obtain a degenerate, but fully functional,
context value.
So a function that is changed to take a context can
still be called by any other normal function just by passing the
result of context.Background(). Moreover this is not a hypothetical
situation; it is very common for me, when I add a context.Context
parameter to a function that did not previously have one, to do
exactly that in real code, to adapt testing code or other callers
that don’t care about the context to the new parameter.
So this “linked list” relationship in practice doesn’t just tend to
break the chain before it hits main, it tends to break the chain very,
very early. It hurts to have to thread changes all the way up, so
we’ve developed a lot of ways of not having to do that and improving
the encapsulation our functions provide. While you may have to do that
sometimes, it’s the sort of rare event that is very cognitively available
and easy to remember and notice, but as a result, is completely outsized
in your memory. In reality it is a very rare case, not the common one.
A normal change can be encapsulated. A very specific change may for some
reason need to go all the way up to main, but this is clearly the exception,
and even then, the rest of the program may use this ability to encasulate
in other ways.
A color change escapes this encapsulation. All functions above it in the
stack must match its requirements.
Color Definition
Any change to a function with the “color” shape of dependencies is a
color. Any change to a function that is shaped like the linked list is
not a color.
While the original defining essay used async as its canonical example,
that does not mean all async in all languages is always a color.
You must examine the specifics of the language’s async support to
see if it matches. In many languages it is not a color. I think with
some current changes to Zig a function that does IO either is
or is not a color depending on
which Io value you pass to it,
or perhaps rather, the color is an attribute of the Io value rather than
the function itself. Async is a color based on whether it fits the definition; the definition
is not that async is always a color.
This is further complicated by the fact that it isn’t always quite
as bright and shining a line as we’d like even in the canonical
“Javascript async” case the original essay is about. It is still
possible to run an async action in a synchronous context is Javascript.
It just comes at the cost of the rest of the async actions that
should be running at the same time. I would consider this “not
really running async at all”, given the staggering nature of the
cost and how it destroys the essential functionality of the feature
and just barely works. However, your mileage may vary. As with
everything, the closer you look, the more it seems to become a spectrum
rather than a binary and the more complicated the spectrum becomes.
However, just because things are spectrums and drawing the line ends
up more complicated than it may have initially seemed doesn’t mean there
aren’t real differences between color changes and normal changes.
Advanced Topic: Partial Coloring
For an advanced understanding, from this point of view we can also
imagine partial colors. Haskell is the king of partial colors,
using them to make powerful guarantees
about what the intervening functions can do by using the color to
constrain them somehow.
For instance, the Software Transaction Memory monad
STM
implements software transactional memory by allowing the creation of
transactional values. These values can only be accessed via functions
like
readTVar
from within an STM monad context.
An STM function can call a pure function which calls a pure function,
but if that pure function needs to then use something in STM, that
propagates up the entire stack between that call and the previous use
of STM, forcing the lifting of every intervening function into the STM
“color”. The color is used to enforce the guarantee that only code in
the STM monad, running through the STM machinery that guarantees
transactionality of the result, can access transactional variables.
However, while that is a non-local, color-like interaction with some of the the
functions above it in the call stack, it does not propagate all the
way to the top of the call stack. The type of a top-level Haskell
program is IO (). STM does not appear in that type signature. Anything
other than IO is guaranteed to be encapsulated somewhere in the
program below the main function itself.
This is indeed a color because this rides on Haskell’s IO color; an
STM transaction can only itself be done in IO, so while the type
system will allow you to pass a TVar a to a pure function, the pure
function will not be able to do anything with it without
unsafePerformIO. Haskell permits you to add “shades” of color to
IO that you can enforce on portions of your codebase.
Originally I tried to write this in terms of the simpler
State
but as I was working through it I realized that State is not a
color. Nothing stops a function from being passed the entire state,
calling some other pure function with that state, and then that pure
function can construct its own State-based computation with it and
if you like you can thread that new state all the way back to the
original context with no problems. That would be klunky and
unidiomatic, but it is both legal and moral.
So not all monads inside of Haskell are colors, just the ones that
can only be resolved within IO somehow. But Haskell does allow you
to create additional “colors” with that mechanism.
I believe it is reasonable to say that most languages do not permit
you to add your own colors to it in this manner. However, again, I
would leave it to the experts in each language to debate whether or
not this is the case.
To close, let me reiterate a color must have this characteristic where
it jumps over intervening calls and forces the change on all other
functions within the relevant island of color. In the STM case above,
anything that wants to access the transactional variables must be in
the STM monad; the type checker will rigidly prevent any attempt to
encapsulate the change to a portion of the calls in between, modulo
unsafe. There is no way for one of the pure functions in the middle
to somehow satisfy the STM constraint for everything below it (modulo
unsafe), which is why it is reasonable to say that it isn’t just
being transmitted up as a linked requirement but actually directly
imposed on all intervening stack frames. If something in the middle
can satisfy the requirement and encapsulate it from functions above
it in principle, even if for some particular code change it may not
be practical for a specific instance, it is not a color.

« Previous
Please Don't Start An Open Source Project, Join One

Recent Posts

Why Function Arguments Are Not Function Colors
Please Don't Start An Open Source Project, Join One
Programming Is Real Engineering, And AI Proves It
When Code Guarantees Are Checked
How Do We Value Code In A World Of Free Code?

Categories

Administrative 
(6)

Bloviation 
(38)

Communication Ethics 
(187)

Frontpage 
(1)

Golang 
(17)

Haskell 
(6)

Iron Lute 
(21)

Jabber 
(17)

Politics 
(14)

Practical Epistemology 
(5)

Programming 
(74)

Reviews 
(4)

XBLinJS 
(10)

Blog Books

Functional Programming Lessons in Imperative Code

Programming Wisdom

The Ethics of Modern Communication

About & Misc

About The Author

© 2026 Jeremy Bowers.

Generated with Hugo and a heavily-modified (blame me, not them) Mainroad theme.

The debate surrounding whether function arguments constitute "function colors" centers on how changes propagate through a call stack in programming, particularly in relation to change isolation principles. While some argue that parameters like Go's context implicitly act as colors because they must be threaded through functions, the author posits a more rigorous definition based on the scope and immediacy of dependency changes.

The text introduces the concept of "Color As A Change Dependency Graph Shape" to formalize this distinction. A change is classified as a color change if altering a function's attribute necessitates an immediate and inescapable reaction from all functions above it in the call stack. In contrast, a normal change only propagates to the direct caller, potentially chaining upward through the hierarchy. Although normal changes can propagate up the stack, they are often encapsulated by intermediate functions, preventing the downstream consequences from reaching higher levels unless absolutely necessary. The most complex scenario—where a color change occurs—represents the worst-case dependency propagation, requiring all functions above it to match the new requirements. However, the author notes that in practical programming, functions frequently break this chain through encapsulation, meaning the color effect is often localized, not globally propagated to the top level.

This distinction relates directly to change isolation. Structured programming relies on the ability to isolate higher-level functions from the internal details of lower-level functions; a "color" change escapes this intended encapsulation because it forces a relationship across the entire dependency chain. For instance, the common pattern seen in languages like Go, where functions can obtain context via calls like context.Background(), demonstrates that linkage often breaks early in the stack, mitigating the need for full stack propagation. When changes are localized, functional isolation is maintained, which aligns with the practical utility of well-structured code.

The definition of a color is therefore tied to its ability to "jump over intervening calls and force the change on all other functions within the relevant island of color." This implies that if an intermediate function can satisfy the requirement and encapsulate the change internally, it should not be considered a color in that context. The author notes that concepts like asynchronous operations introduce further complexity, as whether something constitutes a color depends heavily on the specific language's support and cost implications; for example, running asynchronous actions synchronously introduces costs that might negate the concept of a simple color dependency.

In advanced contexts, such as Haskell's Software Transaction Memory (STM), "partial coloring" is explored. STM uses constraints, acting like colors, to guarantee transactional properties by restricting access to transactional variables only within an STM monad context. While this interaction creates non-local dependencies across the call stack, the color constraint does not necessarily propagate all the way to the top-level type signature. This illustrates that a structure can impose constraints on intervening functions without fully defining their entire external interface, demonstrating that what constitutes a color is less about simple transmission and more about the direct imposition of constraints within the local system. Ultimately, for a dependency to be considered a color, it must rigorously enforce a change across intervening code rather than merely suggesting a linked sequence of operations.