Episode Transcript
Available transcripts are automatically generated. Complete accuracy is not guaranteed.
Speaker 1 (00:00):
Imagine for a second that you are opening up the
back of one of those vintage mechanical pocket watches. You know,
you take the casing off and you just see this
incredibly complex web of tiny golden gears.
Speaker 2 (00:11):
Oh yeah, those are I mean, as a piece of engineering,
they are an absolute marvel to look.
Speaker 1 (00:16):
At it right exactly, But if you turn just one gear,
it instantly turns three others, and then those turn ten more,
which is great for a watch, but as a metaphor
for software architecture, it is an absolute nightmare.
Speaker 2 (00:28):
One hundred percent. It's a total disaster.
Speaker 1 (00:30):
Because, you know, when you're dealing with traditional object oriented
programming or OOP, mutating state in one place often triggers
this like cascade of hidden changes across your entire application.
Speaker 2 (00:41):
Yeah, you change a user's status over in this one module,
and suddenly, completely out of nowhere, a totally unrelated billing
object just breaks over there.
Speaker 1 (00:51):
Exactly. You've basically built a web of change, and trying
to keep the entire state of that mechanical machine in
your head, I mean, it just becomes impossible as the
code based scales up, which is.
Speaker 2 (01:01):
You know why we spend so much time chasing obscure
state bugs instead of actually building new features. We tend
to think we are so neatly categorizing our systems into
these rigid classes and objects, but those implicit connections between
the objects, they eventually just create total architectural gridlock.
Speaker 1 (01:18):
And that gridlock is exactly what we are unpacking in
this deep dive because today we are looking at Michael
Fogus's book Functional JavaScript.
Speaker 2 (01:26):
Yeah, it's such a great text, it really.
Speaker 1 (01:27):
Is, and Focus uses the underscore dot js library to
basically demonstrate a completely different mental model for handling that
complexity we just talked about. Right, we are moving away
from focusing on nouns, you know, the rigid objects and
classes that hold state, and we're shifting our focus entirely
to verbs, pure isolated.
Speaker 2 (01:49):
Function, which is a huge mental shift.
Speaker 1 (01:51):
Huge And even if you're writing in modern E six
or typescript today, the architectural philosophies that Focus lays out
about data transformation, I mean, they are going to completely
change how you approach system design.
Speaker 2 (02:03):
Well in the environment. Here is crucial to understand too,
because JavaScript is multi paradigm. It doesn't force your hand
one way or the other.
Speaker 1 (02:11):
Right, it's very flexible, extremely flexible.
Speaker 2 (02:13):
I mean you can write imperative step by step state mutations.
You can build these massive, prototype heavy object oriented webs
You can even lean heavily into metaprogramming by manipulating the
execution model itself with proxies and reflection. Yeah. But and
this is the key. It also provides the foundational tools
(02:34):
for functional programming right out of the box, specifically through
how it treats functions.
Speaker 1 (02:39):
So rather than that mechanical watch where every gear is
locked into the adjacent gear, Fogus frames functional programming as
like an industrial assembly line.
Speaker 2 (02:46):
I love that analogy me too.
Speaker 1 (02:48):
So raw materials, which is your data, go in one end,
and as they move down the configor belt, they are
transformed by these discrete single purpose machines right the verbs exactly.
And a huge constraint of this assembly line is immutability.
So the original raw materials are never destroyed and they're
never mutated in place. You are always producing a brand
(03:10):
new version of the data at every single station on
the line.
Speaker 2 (03:13):
So let's look at how that actually changes the developer's
day to day approach in the real world. Fogus introduces
a really practical example early on with this parsiage function. Right,
the goal is super simple. You take a user input
string and you parse it into an integer. In a
standard imperative approach, you might write a function that parses
the string. Sure, but it also probably contains hard coded
(03:36):
console dot log statements for debugging oh always yeah, or
maybe direct DOM manipulations to show warning banners on the
screen if the input is malformed.
Speaker 1 (03:45):
I see that pattern all the time. The developer tightly
couples the business logic, which is parsing the age, with
the reporting logic.
Speaker 2 (03:51):
Exactly, and that coupling makes the code incredibly brittle. Think
about it if your environment changes. Say you want to
move that exact same parsiage function from a browser environment
to a no JS.
Speaker 1 (04:02):
Server where there is no dom.
Speaker 2 (04:03):
Right, there is no dom to manipulate, so the function
just completely breaks. Or say you decide you want to
send your errors to a telemetry service like data dog
instead of just a browser console.
Speaker 1 (04:15):
You basically have to rip open the core business logic
of your application just to update your logging sets.
Speaker 2 (04:21):
It's a mess.
Speaker 1 (04:22):
But focuses functional alternative abstracts all of those actions. He
pulls the concepts of warnings and errors and notes out
into their own isolated single purpose.
Speaker 2 (04:33):
Functions be hindy verbs.
Speaker 1 (04:34):
Right, things like a fail function or worn function, and
then the parsiage function just takes those verbs in as arguments.
So if you want to change how the system logs data,
you literally just inject a different worn function into the pipeline.
The actual parsing logic is never ever touched.
Speaker 2 (04:49):
It's basically a textbook application of inversion of control, but
done through functional composition rather than relying on one of
those really heavy op dependency injection frame works.
Speaker 1 (05:00):
Yeah, this can get so glowed.
Speaker 2 (05:02):
They really can't here. Functions just become these perfect hermetically
sealed units of abstraction. They don't know anything about the
outside world, they don't care about the outside world. They
only know what is passed directly into them.
Speaker 1 (05:14):
But to build an entire system out of these isolated verbs,
the language has to support them natively, right like this
hinges entirely on JavaScript treating functions as first class. Yes, now,
I know you listening are probably familiar with the concept
of first class functions, but let's just look at the
mechanical implications of it really quick. Because a function in
(05:36):
JavaScript is literally just an object under the hood. It
can be assigned to a variable, it can be stored
in an array, or like we just saw, it can
be passed as an argument.
Speaker 2 (05:46):
And that capability is basically the engine of applicative programming.
Speaker 1 (05:50):
Applicative programming, Yeah, which.
Speaker 2 (05:51):
Sounds fancy, but it's simply the practice of a function
calling another function that was passed to it. It's the
core mechanism that powers the functional obstrc actions we use
to replace traditional control.
Speaker 1 (06:02):
Flow, right, you mean the holy trinity of map, reduce,
and filter. We use these constantly to replace clunky forour loops.
But I want to push back on the architectural cost
of this for a second.
Speaker 2 (06:13):
Okay, laid on me.
Speaker 1 (06:14):
If we are strictly following this functional assembly line model,
we are constantly passing functions around, We're creating closures. We
are allocating memory for brand new arrays every single time
we call map or filter, all because we refuse to
mutate the original array.
Speaker 2 (06:30):
Right, to maintain immutability.
Speaker 1 (06:31):
Yeah, but if I just write a standard imperative for
loop that mutates an array in place, I am mathematically
utilizing way fewer CPU cycles and I'm allocating way less memory.
So at scale, doesn't treating everything as this immutable assembly
line of tiny functions just absolutely tank the application's performance.
Speaker 2 (06:51):
It's a totally valid architectural concern, and honestly, historically, in
older runtime environments, you would be absolutely right. The overhead
of the call stack alone plus the garbage collection from
all those new arrays would be a massive bottleneck, right.
Speaker 1 (07:04):
It would just choke the system.
Speaker 2 (07:05):
But the mechanics of modern JavaScript engines, particularly Googles V
eight engine, you know, the one that powers Chrome and Node,
they drastically alter that math through runtime compilation strategies.
Speaker 1 (07:17):
Okay, walk me through the mechanics of that, because how
does vight actually compensate for the overhead of hundreds of
tiny function calls.
Speaker 2 (07:26):
Well, it all comes down to how just in time
or JIT compilation works. You see, V eight doesn't just
blindly execute your code line by line. It actually watches
the code execute, it gathers profiling data, wall it runs,
and then it dynamically optimizes it.
Speaker 1 (07:42):
Wait, it optimizes it on the fly exactly.
Speaker 2 (07:45):
And one of the main tools it uses to do
this is called inline caching. So if V eight sees
that a specific tiny function like say our isolated warn
function from earlier. If it sees that function being called
repeatedly with the same exact types of arguments, it will
eventually just stop executing it as a separate function call.
Speaker 1 (08:00):
Wait, so it actually rewrites the execution path.
Speaker 2 (08:03):
Yes, literally, it performs what's called code inlining. The compiler
takes the actual machine code instructions of that tiny worn
function and pastes them directly into the calling functions execution block.
Speaker 1 (08:15):
Oh wow.
Speaker 2 (08:16):
Yeah, so the overhead of the function call, the context switching,
pushing of popping variables from call stack, all of that
is entirely eliminated. V eight essentially flattens your beautiful, modular
functional code into this highly optimized, imperative machine code block
under the hood.
Speaker 1 (08:32):
That completely changes the perspective on performance. So their argument
that a massive fore loop is always faster that completely
ignores how the modern engine actually optimizes the code.
Speaker 2 (08:41):
Well yeah, and in fact, a massive forur loop that
does like five different things and mutates variables of all
different types can actually confuse the GI compiler. Oh really yeah.
Vight relies heavily on what are called hidden classes, which
are basically predictable object shapes to optimize property access. If
your imperative loop is constantly altering the shape of your objects,
(09:02):
you know, adding or deleting properties dynamically, vight will actually
de optimize that block of code. It kicks it back down, exactly,
It kicks it back down to slow, unoptimized execution. But
by writing small, pure functions that always return predictable data shapes,
you are actually feeding the V eight engine exactly what
it needs to optimize your code to near native speeds.
Speaker 1 (09:24):
That is fascinating.
Speaker 2 (09:25):
Do you get the developer experience of highly modular code
without having to sacrifice the run time performance?
Speaker 1 (09:30):
Okay, so the engine basically has our back on the
performance side, But let's look at the developer experience of
actually snapping these functions together. Because you know, in the
real world they rarely fit perfectly out of the box.
Speaker 2 (09:41):
No, they definitely do not.
Speaker 1 (09:42):
So what do we do when two systems need to
talk but their function signatures are completely mispatched. Fogus dives
into this problem by looking at JavaScript's native array sorting mechanism,
which is notoriously quirky.
Speaker 2 (09:54):
Oh it is a perfect case study and weird API design.
If you take an array of integers, say two, three,
negative six, zero, negative one oh eight, and forty two, okay,
and you just call the native dot sort method on
that array. It doesn't actually sort them numerically, right.
Speaker 1 (10:11):
Which drives beginners crazy. It sorts them lexicographically. It coerces
everything to a string first, so negative one oh eight
comes before negative six, because the character one comes before
the character six in unicode. It's this bizarre historical artifact
from JavaScript's early days, when it was mostly just dealing
with string data from HTML forms, and so to get
(10:31):
a mathematical sort, the API forces you to pass in
a very specific comparator function.
Speaker 2 (10:37):
And the contract for that comparator function is incredibly rigid, right.
It expects exactly two arguments, and it must return a
negative number if the first is smaller, a positive number
if it's larger, and zero if they're totally equal.
Speaker 1 (10:51):
Which means usually developers just write this messy inline anonymous
function with some subtraction math or like an i false
block jammed directly inside the torque call.
Speaker 2 (11:01):
Yeah, we've all written that code.
Speaker 1 (11:02):
Oh totally. But Fogus abstracts this to show the power
of functional composition. He starts by writing a super generic
predicate function called less equal. It just takes two numbers
and returns a boolean just true.
Speaker 2 (11:13):
Or false, right, and a predicat being any function that
takes a value and just returns a boolean. It is
basically the simplest, most reusable piece of logic you can
possibly write.
Speaker 1 (11:23):
But the SARDAPI doesn't want a boolean.
Speaker 2 (11:25):
No, it does not.
Speaker 1 (11:26):
It will completely fail if you just pass it less
or equal. So Fogus writes a higher order function, which
is a function that returns another function, and he calls
it comparator. This comparator function takes any boolean predicate as
its argument, and it returns a brand new function that
maps the true false output into the negative one, zero
or one format that the dot sort API demands.
Speaker 2 (11:47):
The mechanism there is what's really fascinating, Yeah, because you
are physically decoupling the core business logic, you know, evaluating
if a is less than b from the arbitrary framework
requirement of those nig one, zero one return values.
Speaker 1 (12:02):
It's exactly like a travel adapter. That's a good way
to look at it, right, Like if I take my
laptop to Europe, the wall socket, which is the dot
sort API, it demands a specific three prong shape my
laptop charger, which is my less equal predicate. It only
has a two prong shape. I don't rewire my laptop
charger to fit the European socket.
Speaker 2 (12:20):
No, that would ruin it for when you go back
home exactly.
Speaker 1 (12:23):
Instead, I just plug my charger into an adapter, and
then I plug the adapter into the wall. Focus's comparator
function is that travel adapter.
Speaker 2 (12:30):
And by not rewiring the core logic, that lesser equal
predicate remains completely pure and completely reusable. You can use
it tomorrow inside a filter function or a su function,
which actually do expect boolean returns. If you had buried
that logic inside of bespoke comparator, it would be locked
away forever and totally useless to the rest of your application.
(12:53):
And that is really the essence of functional composition, right.
You build complex behavior just by snapping the gether these tiny, generic,
re usable verbs.
Speaker 1 (13:02):
But let's look at the other side of the equation,
because if our verbs are these tiny decoupled adapters, what
happens to our nouns? I mean, the prevailing philosophy in
most enterprise software is object oriented design, where the data
and the methods that operate on that data are all
bundled together into these massive classes. If we are decoupling
all the verbs, do we just toss out the classes entirely.
Speaker 2 (13:22):
Well, this is where Fogus introduces a concept called data thinking,
and it aggressively challenges standard op doctrine. He actually quotes
Alan Perlis, you know, one of the pioneers in computer
science who famously said it is better to have one
hundred functions operate on one data structure than ten functions
on ten data structures.
Speaker 1 (13:42):
Wow. That completely flies in the face of how most
developers are taught to model their domains. Oh, completely, because
we're taught that if you have a user, you build
a user class. If you have an invoice, you build
an invoice class.
Speaker 2 (13:53):
And the fundamental problem with that approach is that you
are basically inventing a brand new, highly specific micro language
for every single entity in your entire Systemma. Well, a
user class might have a dot get role method, but
an invoice class has a dot calculatol method. If you
want to write a utility function to format some data,
(14:15):
it has to be custom tailored to understand the specific
bespoke methods of the user class or the invoice class.
Your logic becomes completely coupled to the arbitrary shape of
that specific class.
Speaker 1 (14:25):
Ah I see, So Fogus argues that we should just
strip all of that away and rely on bare fundamental
data primitives.
Speaker 2 (14:32):
Yes, just plain JavaScript objects, which are essentially just associative
arrays of key value pairs and standard arrays.
Speaker 1 (14:40):
He demonstrates this with this lame CSV parsing example. So
imagine a simple data set of employees. They've got names, ages,
and departments. A traditional OP approach would parse that CSV
and instantiate like a table class, which then holds an
array of row classes which are mapped to individual employees classes.
Speaker 2 (15:00):
Which creates a massive memory footprint, not to mention the
sheer amount of boilerplate code required just to define all
this classes in the first place.
Speaker 1 (15:06):
Right, It's exhausting. But Fogus bypasses all of it. He
just parses the CSV directly into an array of plane
JavaScript objects, and that's it. That's the entire data model,
an array of objects. But okay, here is where I
have a major issue with this data thinking concept. If
you strip away all the classes, you are also stripping
away all the structural guarantees in a large code base.
(15:28):
If I pass an employee object into a function, the
class definition guarantees that the employee has an email property,
and it guarantees that the email is a string. Right
if we just throw raw arrays of loose objects around
the code base, aren't we basically just crossing our fingers
and hoping the data is shaped correctly? Like how do
you prevent the whole system from collapsing due to missing
(15:51):
properties without the strict contracts of a class.
Speaker 2 (15:53):
So that right there is the defining trayoff between the
two paradigms. In an OP world, the validation happens internally
within the object itself. The object protects its own state exactly.
But in a functional data thinking world, you rely on
validation at the boundaries of your system.
Speaker 1 (16:11):
Okay, what do you mean by the boundaries?
Speaker 2 (16:13):
Think about when the data actually enters your application. Say
you're parsing adjason payload from an API request or you're
reading that CSV file from earlier. That is your boundary.
So you write robust validation functions right there at the edge.
If the data fails the schema check at the boundary,
you reject it immediately. But once the data passes the
boundary and enters your internal functional assembly line. You just
(16:34):
trust it. You don't need heavy classes to babysit the
data internally because you've already rigorously verified its shape at
the door.
Speaker 1 (16:41):
Oh I see, So you decouple the structural validation from
the runtime behavior exactly. And once you do that, the
real power of that Alan Perliss quote really clicks into
place because your data is completely uniform. It's just planarrays
and objects, which means every single generic function in your
toolkit can operate on it.
Speaker 2 (17:00):
Focus showcases this brilliantly by building an entire pseudosequal querying
engine using just functional concepts operating on these simple arrays
of objects.
Speaker 1 (17:09):
Yeah, this part of the book is an absolute masterclass
because he builds the suite of functions to manipulate the
data as if it were an actual relational database. Like
he writes a project function which maps directly to a
seql select statement. You pass it your array of employee
objects and an array of keys you want, like say
name and department, and it just maps over the collection
(17:31):
and returns a brand new array of objects containing only
those specific keys.
Speaker 2 (17:36):
And then he writes an as function which acts exactly
like a sequel alias mapping over the array to seamlessly
rename specific keys in the returned objects, and.
Speaker 1 (17:45):
A restrict function, which is essentially just a seql wear clause.
It uses an underlying filter to evaluate a predicate against
each object, returning an array of only the objects that
actually pass the test.
Speaker 2 (17:57):
But the mechanical brilliance here is the composability. The project
function takes an array of objects as an argument, and
it returns inn array of objects. The restrict function is
the exact same thing. Because their inputs and outputs share
the exact same primitive data structure. They just chain together flawlessly.
Speaker 1 (18:14):
You can pass the result of restrict directly into project,
and then the result of that directly into As you
are executing highly complex relational queries against your data without
a single custom class, without a heavy RM, and critically
without ever mutating the original data set. It's just beautiful.
Speaker 2 (18:31):
It's just data flowing smoothly through an assembly line of
pure verbs.
Speaker 1 (18:34):
Right. And to address your earlier concern about memory overhead,
because JavaScript passes objects by reference, returning a new array
from restrict or project doesn't actually mean you are duplicating
the actual object data in memory over and over. Oh really, Yeah,
you are simply creating a new array that contains pointers
to the existing objects. So the memory overhead is surprisingly minimal,
(18:58):
But the architectural safety you gain by not mutating that
original array is just massive.
Speaker 2 (19:03):
Which perfectly transitions into the final core concept that focus tackles,
which is the existential threat to all of this functional safety,
the big one, Because if we have this pristine assembly
line where data flows through isolated machines without ever being destroyed,
what is the single biggest danger to that factory? Mutability,
specifically mutability in the global scope.
Speaker 1 (19:24):
And JavaScript is uniquely terribly vulnerable to this due to
how its lexical environment was originally designed.
Speaker 2 (19:31):
Yeah, if you declare a variable without strict bindings using
the var keyword historically or just completely omitting a declaration
keyword entirely, the JavaScript engine handles that missing reference in
a very, very dangerous way.
Speaker 1 (19:47):
Let's trace that execution path for a second. When the
interpreter hits a variable assignment that wasn't properly declared to
say global state equal true, it doesn't just crash.
Speaker 2 (19:57):
No, it tries to be helpful.
Speaker 1 (19:59):
Right. It looks at the current execution contexts environment record,
it doesn't find global state there, so it traverses up
the scope chain to the outer lexical environment, and it
just keeps moving up the chain until it hits the
global execution context.
Speaker 2 (20:13):
And if it doesn't find it declared there either. Instead
of throwing a reference er, the engine quietly invisibly attaches
that variable directly to the global.
Speaker 1 (20:21):
Object, which is terrifying.
Speaker 2 (20:23):
It is in a browser, that's the window object. In
node it's the global object.
Speaker 1 (20:27):
And Fogus illustrates this with a terrifyingly simple example. A
variable silently leaks to the global scope, so he writes
a global variable lavin la vida global. Now, because it
exists on the global object, it is fully accessible to
every single function, every module, and every third party script
(20:48):
running anywhere in that environment.
Speaker 2 (20:50):
Which means literally any other piece of code may be
written by a completely different developer on another team can
execute an assignment like a global variable.
Speaker 1 (20:59):
I drink your monad, and just like that, the state
is overwritten instantly without any warning whatsoever. No, and because
object oriented programming relies so heavily on internal state mutations,
if your classes depend on any external context, they are
highly highly susceptible to these invisible side effects. Yeah, when
your application inevitably crashes because a booleon suddenly became a string,
(21:22):
the stack trace isn't going to tell you who drank
your milkshake. It will only tell you that the code
failed three steps later when it tried to evaluate the string.
It's the broken tooth on the mechanical pocket watch all
over again.
Speaker 2 (21:33):
And the functional programming heavily mitigates this exact problem through
the principle of referential transparency. A pure function does not
read from the global scope, and it absolutely does not
write to the global scope. Its output is derived entirely
and exclusively from the arguments passed explicitly into it. If
(21:54):
a function needs configuration data, you have to pass that
data in as an argument.
Speaker 1 (21:59):
You don't just reach out into the ether to grab it.
You enforce a very strict contract of explicit inputs and
explicit return values. Literally, no one can overwrite your data
because you never expose a mutable reference to it. You
only ever return a newly transformed copy.
Speaker 2 (22:15):
And when you apply that discipline across an entire code base,
you just eliminate entire categories of bugs. The cognitive load
required to understand the system drops dramatically because you don't
have to keep this massive mental map of what else
might be secretly modifying your variables.
Speaker 1 (22:29):
You just look at the assembly line exactly.
Speaker 2 (22:31):
You just observe the data flowing from one station to
the next.
Speaker 1 (22:34):
It really highlights how focus is functional. JavaScript is so
much less about learning the syntax of a specific library
like Underscore and way more about adopting this defensive, scalable
engineering mindset. For sure, we've explored how JavaScript's multi paradigm
nature allows us to basically shift away from rigid oop structures.
We unpacked how first class functions power those applicator techniques,
(22:58):
and how modern jit can like vight, make these abstractions
incredibly performant under the hood.
Speaker 2 (23:04):
Yeah, and we saw how higher order functions can act
as adapters to decouple our logic from framework constraints, and
how data thinking allows us to model complex you know,
pseudoseqal pipelines using literally nothing but primitive rays, plane objects,
and highly composable verbs.
Speaker 1 (23:19):
And ultimately we saw how enforcing a mutability and avoiding
the global scope chain protects our systems from unpredictable state mutations.
It's an entirely different way of modeling the world, which
actually leaves me with a final thought for you to
consider as you step away from this.
Speaker 2 (23:33):
Deep dive to curious.
Speaker 1 (23:34):
Well, we spent this whole time talking about software architecture, right,
but the principles of object oriented versus functional design apply
really heavily to how we structure human systems too.
Speaker 2 (23:45):
Wait, how so think.
Speaker 1 (23:46):
About corporate structures. We very often organize our teams using
rigid object oriented principles. We build these complex, heavy hierarchies,
you know, VP classes, director classes, manager classes, and we
lock specific responsibilities and information silos deep inside those titles.
Speaker 2 (24:03):
That is painfully accurate.
Speaker 1 (24:05):
Right, and then when the market shifts and a totally
new problem arises, our rigid human classes struggle to adapt
because all the communication pathways are so tightly coupled to
the hierarchy.
Speaker 2 (24:16):
We end up spending months trying to rewrite the org
chart just to solve the new problem.
Speaker 1 (24:20):
Exactly, Well, what if we apply data thinking to our teams.
What if we stripped away the rigid hierarchies and viewed
our organizations as functional assembly lines. The raw material is
the project, The verbs are the specific skills of the
individuals on the team completely regardless of their title.
Speaker 2 (24:38):
That's a really powerful way to look at it.
Speaker 1 (24:39):
Could we build more resilient, adaptable companies if we stopped
obsessing over managing the state of our org charts and
started focusing on building better pipelines for our people's skills
to flow through. So next time you're stuck in a
bureaucratic bottleneck, ask yourself, are we acting like a broken
pocket watch or are we building an assembly line?