Speaker 1: Imagine you're curled up in your favorite chair right deeply
Speaker 1: engrossed in this fantastic mystery novel.
Speaker 2: Oh it sounds like a great afternoon.
Speaker 1: Yeah, exactly. You're following the plot, picturing the characters. But
Speaker 1: there's just one massive problem.
Speaker 2: Okay, what is it?
Speaker 1: Someone else is standing right next to you frantically flipping
Speaker 1: the pages of your book back and forth while you're
Speaker 1: trying to read it.
Speaker 2: Oh wow, that sounds incredibly frustrating.
Speaker 1: Right, I mean, one second you're on chapter three and
Speaker 1: then boom, you're staring at the epilogue.
Speaker 2: Yeah, that would drive me crazy, exactly.
Speaker 1: And that chaotic, unpredictable, pull your hair out mess that
Speaker 1: is exactly what happens inside your software when concurrent programs
Speaker 1: share mutable state.
Speaker 2: It really is the ultimate concurrency nightmare. You know, you
Speaker 2: have one thread trying to read a sequence of data,
Speaker 2: and then another thread is mutating or like physically changing
Speaker 2: that exact same data right out from under you. Yeah,
Speaker 2: and it's the root cause of so many unpredictable bugs.
Speaker 1: Today's deep dive is all about stopping that page flipping.
Speaker 1: We're exploring the surprisingly practical world of functional programming, but
Speaker 1: we're doing it inside language. Most people associate strictly with
Speaker 1: object oriented programming, which is C shark, right, and we're
Speaker 1: pulling our insights from Enrico Banana's book Functional Programming in
Speaker 1: C Sharp. The mission here is to basically act as
Speaker 1: a shortcut for you, because you absolutely do not need
Speaker 1: a PhD in mathematics to use these concepts.
Speaker 2: No, you really don't.
Speaker 1: We're going to look at how functional techniques can you know,
Speaker 1: drastically reduce bugs, handle concurrency almost effortlessly, and just make
Speaker 1: your code based so much more readable.
Speaker 2: Yeah, and I think that's important because functional programming often
Speaker 2: gets this unfair reputation for being overly academic.
Speaker 1: Yeah, totally, like it's just Ivory Tower stuff exactly.
Speaker 2: But when you actually apply it to real world line
Speaker 2: of business applications in C sharp, it becomes this highly
Speaker 2: pragmatic tool set. It really boils down to changing two
Speaker 2: ingrained like how we treat our data and how we
Speaker 2: treat our behavior.
Speaker 1: Well, let's start with behavior then, because that ties right
Speaker 1: into how we manage the flow of an application.
Speaker 3: In c sharp.
Speaker 1: Functions aren't just invisible blocks of code anymore. They are
Speaker 1: what we call first class values, which means you can
Speaker 1: take a function, assign it to a variable, or pass
Speaker 1: it as an argument into another function, just like you
Speaker 1: would with a regular string or an integer.
Speaker 2: Yeah, and we use delegates and lambed expressions to do this,
Speaker 2: and treating functions as data. It unlocks one of the
Speaker 2: most foundational tools in functional.
Speaker 1: Programming, a higher order function.
Speaker 2: Exactly the higher order function people usually just shorten it
Speaker 2: to HOF. HOF is simply any function that either takes
Speaker 2: another function as an input parameter or returns a function
Speaker 2: as its output.
Speaker 1: Right, And thinking about this in real world terms, using
Speaker 1: a HOF feels a lot like hiring a subcontractor oh
Speaker 1: I like that? How so Well, take the lanq wear method,
Speaker 1: which we use to filter lists all the time in
Speaker 1: C sharp. In this scenario, where's the manager. It handles
Speaker 1: all the tedious border plate stuff, setting up a loop,
Speaker 1: managing the index. You know, all the mechanical parts.
Speaker 2: Right, the stuff you don't want to write over and
Speaker 2: over exactly.
Speaker 1: Yeah, But the manager doesn't actually know what you're looking for,
Speaker 1: so it subcontracts the decision making logic asking like should
Speaker 1: I keep this specific item? It delegates that to the
Speaker 1: function that you pass into it.
Speaker 2: That is a perfect analogy. The division of labor is
Speaker 2: so clear. The hof handles the mechanics and you supply
Speaker 2: the unique business logic.
Speaker 1: Yeah.
Speaker 2: Another incredibly practical application for this is encapsulating set up
Speaker 2: and tear down logic. Think about how often you interact
Speaker 2: with the database. Oh, all the time, right, and you
Speaker 2: always have to do the same dance. You acquire a connection,
Speaker 2: open it, do your work, close it, and then dispose
Speaker 2: of the resource.
Speaker 1: Oh man, the endless using blocks You end up copying
Speaker 1: and pasting those same five lines of code across like
Speaker 1: fifty different repositors.
Speaker 2: So repetitive. But with a higher order function, you can
Speaker 2: just write a custom using function one single time. Oh right.
Speaker 2: That one function handles opening the connection and disposing of
Speaker 2: it in a finally block, and you just pass it
Speaker 2: a function that represents the specific query you want to
Speaker 2: run in the middle.
Speaker 1: That's brilliant. It completely eliminates all that boilerplate duplication exactly.
Speaker 1: So Okay, the manager handles the subcontractors, the code gets cleaner.
Speaker 1: But even the best manager can't fix a job site
Speaker 1: if the blueprints keep magically changing while the crew is
Speaker 1: reading them.
Speaker 2: Right back to the page flipping problem.
Speaker 1: Exactly which brings us to this second habit.
Speaker 3: We have to change.
Speaker 1: We have to avoid state mutation, right.
Speaker 2: And mutating state simply means changing and existing value in
Speaker 2: place and memory. For instance, if you have a list
Speaker 2: of numbers in C sharp and you call list sort,
Speaker 2: the framework physically rearranges those items in your computer's memory.
Speaker 1: Right, it's a destructive update. The original order is just
Speaker 1: gone forever.
Speaker 2: But if you use LA and Q's order by method,
Speaker 2: it behaves complete lely differently. It doesn't touch your original list.
Speaker 2: It reads it and then creates a brand new list,
Speaker 2: a new view of the data that happens to be sorted.
Speaker 1: Leaving the original completely intact.
Speaker 2: Exactly, And that distinction might seem minor, but it is
Speaker 2: exactly what saves you from the concurrency disaster. Buanano's book
Speaker 2: has this terrifying example of this.
Speaker 1: Oh yeah, the math there one, right.
Speaker 2: Imagine a background task Task one. It's trying to calculate
Speaker 2: the sum of a massive list of numbers running from
Speaker 2: positive ten thousand all the way down to negative ten thousand.
Speaker 1: Okay, so logically, if you add all those up, the
Speaker 1: sum should perfectly balance out to zero.
Speaker 2: It should, But imagine that at the exact same millisecond.
Speaker 2: Task two is concurrently running on a different thread, and
Speaker 2: it calls list sort on that exact same list.
Speaker 1: Oh no, because list sort mutates the list in place.
Speaker 1: Task one is trying to do math, while task two
Speaker 1: is aggressively moving the numbers around exactly.
Speaker 2: Task one might read the number five thousand, and then
Speaker 2: task two moves that five thousand further down the.
Speaker 1: List, and this one just reads it a second time.
Speaker 2: Right, so the result isn't zero, the book points out,
Speaker 2: You get these bizarre, totally unpredictable results, like returning a
Speaker 2: sum of nine, three hundred thirty nine and seventy.
Speaker 3: That is wild.
Speaker 2: And this is why concurrency and traditional imperative programming is
Speaker 2: so notoriously difficult. You have to introduce complex locks, You
Speaker 2: have to freeze the entire list just to read it,
Speaker 2: which just destroys your performance.
Speaker 1: But if you embrace immutable data, where sorting creates a
Speaker 1: new list instead of destroying the old one, concurrent reads
Speaker 1: don't cause any inconsistencies whatsoever.
Speaker 2: Right, you get parallelization essentially for free, because nobody is
Speaker 2: flipping the pages of the book you are trying to read.
Speaker 1: I mean, if you've ever stared at a massive, tangled
Speaker 1: legacy code base and felt sheer panic at the thought
Speaker 1: of making it thread safe. Realizing that avoiding mutation gives
Speaker 1: you a free pass to parallel execution is just a
Speaker 1: massive light bulb moment.
Speaker 2: It really is.
Speaker 1: And taking this idea of avoiding mutation to its logical
Speaker 1: extreme brings us to the concept of the pure function.
Speaker 2: Yes, the pursuit of purity is central here. A pure
Speaker 2: function closely resembles a mathematical equation. It operates under two
Speaker 2: very strict rules. First, its output depends entirely and exclusively
Speaker 2: on its input arguments okay, And second, it causes absolutely
Speaker 2: no side effects.
Speaker 1: Now, side effects is a really broad category, right, We
Speaker 1: aren't just talking about mutating global variables. In functional programming,
Speaker 1: throwing an exception is considered a side effect. Yes, it
Speaker 1: is performing any kind of IO like printing to the console,
Speaker 1: querying a database, or even just checking the system clock.
Speaker 3: Those are all side effects.
Speaker 2: Right, And people always ask why is checking the clock
Speaker 2: a side effect? It's because anytime you rely on the
Speaker 2: external physical world, your function loses its determinism. Oh, if
Speaker 2: a function asks the system for the current time to
Speaker 2: validate a token, it will return a different result tomorrow
Speaker 2: than it did today, even if you pass in the
Speaker 2: exact same token.
Speaker 1: Right, whereas a truly pure function guarantees the exact same
Speaker 1: output every single time until the end of the universe exactly.
Speaker 2: The book contrasts two string manipulation functions to show why
Speaker 2: this matters for scaling up. One is a pure function
Speaker 2: called toe sentence case. It just takes a string and
Speaker 2: formats the capitalization super simple. The other is an impure
Speaker 2: function called pre pend counter. It takes a string, but
Speaker 2: it also reaches outside itself to use the plus plus
Speaker 2: operator to increment a shared global counter variable, so it
Speaker 2: can number the strings.
Speaker 1: And if you try to run that impure pre pen
Speaker 1: counter in parallel across a million strings, it falls apart
Speaker 1: because at the CPU level, plus is actually multiple steps
Speaker 1: read the value, add one, write it back.
Speaker 2: It's not atomic, right, Multiple threads read the shared counter
Speaker 2: at the exact same millisecond, they all add one to
Speaker 2: the same base number and write it back. They trample each.
Speaker 1: Other, so you lose thousands of updates.
Speaker 2: Yeah, your final count will be completely wrong. But the
Speaker 2: pure to sentence case function, you can spread that across
Speaker 2: one hundred different CPU cores instantly.
Speaker 1: Because it doesn't rely on anything else side itself, it
Speaker 1: is completely isolated, which means it's thread safe by default exactly.
Speaker 2: And that complete isolation is why pure functions are an
Speaker 2: absolute dream to unit test. You don't need to configure
Speaker 2: dependency injection or complex mock objects. You just feed it
Speaker 2: an input and assert the output.
Speaker 1: Whereas testing in pure io is usually a nightmare. To
Speaker 1: test a service that happens to check the time, developers
Speaker 1: usually resort to creating these totally hollow header interfaces. You
Speaker 1: end up creating an I date time service and wiring
Speaker 1: it up everywhere solely so you can inject a fake
Speaker 1: frozen clock into your unit test to mock DateTime dot
Speaker 1: ut as nail.
Speaker 2: It hadds so many layers of boilerplate just to test
Speaker 2: a simple date validation. The functional approach solves this by
Speaker 2: pushing the pure boundary outwards.
Speaker 3: What do you mean by outwards?
Speaker 2: Instead of injecting a massive mocked interface that goes out
Speaker 2: and gets the date, just inject a hard coded daytime
Speaker 2: value directly into your validator as an argument.
Speaker 1: Oh, so the val itself stays.
Speaker 2: Pure exactly, Or if you need to validate against a
Speaker 2: list of valid bank routing numbers from a database. Don't
Speaker 2: inject an entire I repository interface. Just in ject a
Speaker 2: simple function like a delegate returning a list of strings
Speaker 2: that the validator can invoke.
Speaker 1: Okay, wait, I'm trying to picture an application built entirely
Speaker 1: like this, a program with absolutely no IO.
Speaker 3: It's basically just a space heater.
Speaker 1: A space heater, yeah, I mean it consumes electricity, gets
Speaker 1: a CPU hot, and does absolutely nothing useful. We have
Speaker 1: to have side effects, right, we have to save to
Speaker 1: databases and display things on screens.
Speaker 2: That is a very valid reality check, and functional programming
Speaker 2: absolutely acknowledges that the goal was never to eliminate IO entirely.
Speaker 2: It is to isolate it.
Speaker 3: Okay.
Speaker 2: The book uses a document converter application called pandock to
Speaker 2: illustrate this architecture. You isolate the pure computational core, the
Speaker 2: complex logic of parsing, markup, translating, syntax, formatting, the layout
Speaker 2: that is all pure, and then you wrap that core
Speaker 2: in an impure IO shell. The shell handle reading the
Speaker 2: original file from the hard drive and writing the final
Speaker 2: PDF back to the disc.
Speaker 1: Oh I see, so you actively push the MESSI impurity
Speaker 1: to the very edges of your application exactly.
Speaker 2: You keep the messy real world on the outside and
Speaker 2: your core business logic pure on the inside.
Speaker 1: But that raises a major question about error handling. If
Speaker 1: our pure functions aren't allowed to throw exceptions, how do
Speaker 1: we handle bad data?
Speaker 2: Right?
Speaker 1: Like, if someone passes my pure function and negative age
Speaker 1: and I can't throw an argument exception, what are we
Speaker 1: supposed to do?
Speaker 2: This brings us to the concept of making our function
Speaker 2: signatures honest. A signature, its name, inputs, and return type
Speaker 2: is essentially a contract, right.
Speaker 1: It tells the caller what inputs it requires and what
Speaker 1: output it guarantees.
Speaker 2: Exactly, and the book highlights how easily we lie in
Speaker 2: these contracts. There's an example of a dishonest signature, a
Speaker 2: function named calculator risk profile that takes the standard integer
Speaker 2: for the age parameter.
Speaker 3: Okay.
Speaker 2: The signature promises that if you give it any integer,
Speaker 2: even negative one thousand, it will successfully return a risk profile.
Speaker 2: But inside the body it checks if the age is negative.
Speaker 2: If it is, it throws an argument exception and halts
Speaker 2: the program.
Speaker 1: It's a completely dishonest contract. The caller reads the signature
Speaker 1: assumes they're safe providing any integer, and then at runtime
Speaker 1: the app violently crashes.
Speaker 2: Exactly functional programming actively avoids hidden traps like this.
Speaker 1: So how do we fix a dishonest signature without throwing
Speaker 1: an exception? We use a pattern called a smart constructor.
Speaker 1: Instead of accepting a raw integer for age, we define
Speaker 1: a custom immutable age type and we write a smart
Speaker 1: constructor method like age two seat. This runs the validation
Speaker 1: logic before the age object is ever allowed to be created.
Speaker 2: By enforcing that validation upfront, you make invalid states unrepresentable
Speaker 2: in your system. Once your code holds an instantiated age object,
Speaker 2: you know for a fact it contains a valid positive age.
Speaker 1: So your calculat risk profile function changes its signature to
Speaker 1: require an age type instead of integer.
Speaker 2: Yep, the signature becomes completely honest and the function never
Speaker 2: has to worry about throwing an exception for negative numbers. Ever.
Speaker 1: Again, that makes perfect sense for a validation. But what
Speaker 1: about retrieving missing data? Because the standard dot net collections
Speaker 1: drive me a little crazy with their inconsistency here how so, Well,
Speaker 1: if I use a name value collection and ask for
Speaker 1: a dictionary key that doesn't exist like green, it quietly returns.
Speaker 3: A null value.
Speaker 1: But if I use a standard dictionary and ask for blue,
Speaker 1: it throws a keynot found exception.
Speaker 2: Oh yeah. Under the functional paradigm, both those outcomes, returning
Speaker 2: a silent null or throwing a loud exception are considered dishonest.
Speaker 2: If a function goes looking for a value and might
Speaker 2: not find it, its signature needs to explicitly declare that
Speaker 2: failure is a distinct possibility. This is where we introduce
Speaker 2: the option type.
Speaker 1: The option type you might hear it called maybe in
Speaker 1: other languages. It essentially models data that might be absent yes.
Speaker 2: It has two possible states, some and none.
Speaker 1: I like to vision the option type as a cardboard
Speaker 1: shipping box. You order a new gadget online. When the
Speaker 1: delivery arrives, the box either contains the gadget you wanted.
Speaker 1: That's the sum state, or there was a warehouse mix
Speaker 1: up and the box is empty, which represents the nun state.
Speaker 1: That's a great visualization, but crucially, either way, the delivery
Speaker 1: driver still handed you a physical box. You can safely
Speaker 1: hold it, pass it to another department, and write code
Speaker 1: to handle the box without your program crashing.
Speaker 2: Exactly, instead of returning null, which is just a ticking
Speaker 2: time bomb that causes a null reference exception. The second
Speaker 2: you try to access it you return an option. It
Speaker 2: explicitly communicates to the developer the statu might be absent.
Speaker 2: You are forced to handle the empty box scenario.
Speaker 3: Hang on, though.
Speaker 1: Let's say a business requirement changes marketing decides that the
Speaker 1: first name field on a massive newsletter sign up form
Speaker 1: should now be optional. If I go into my C
Speaker 1: sharp domain model and change the first name property from
Speaker 1: a regular string to an option string, every single piece
Speaker 1: of code in my application that touch's first name is.
Speaker 3: Going to break immediately. It won't even compile.
Speaker 1: How is breaking the bild a good thing?
Speaker 2: It is actually a fantastic thing, because you're trading silent,
Speaker 2: unpredictable run time crashes for loud immediate compile time errors.
Speaker 2: Think about the alternative. If you just allowed that string
Speaker 2: to be null, your code would compile perfectly fine, but
Speaker 2: it would crash in production a week later the very
Speaker 2: first time user didn't enter their name and a downstream
Speaker 2: system tried to call to Hupper on it ah right.
Speaker 2: By changing the signature to an option, the compiler becomes
Speaker 2: your personal assistant. It flags every single place in your
Speaker 2: massive codebase that needs to be updated to acknowledge this
Speaker 2: new optional reality.
Speaker 1: Okay, I see the safety in that. Yeah, but it
Speaker 1: brings up a huge logistical headache.
Speaker 2: What's that?
Speaker 3: If we safely pack our.
Speaker 1: Missing data into these option shipping boxes, how do we
Speaker 1: actually do any work on the data inside the box.
Speaker 1: Like if I have a pure function that expects a
Speaker 1: regular string, but my database returned in an option string,
Speaker 1: I can't just pass the box to the function. Do
Speaker 1: I have to write an if statement every single time
Speaker 1: to check if the box is empty, unpack it, run
Speaker 1: the function, and repack it. That sounds exhausting it.
Speaker 2: Would be, but you don't have to do that manually.
Speaker 2: That is exactly where we introduce the core functional design
Speaker 2: patterns functors and monads, which are operated by the map
Speaker 2: and bind functions.
Speaker 1: I know, the terms functors and monads sound incredibly intimidating.
Speaker 1: They sound like advanced category theory.
Speaker 2: They do carry a lot of academic baggage, but stripped
Speaker 2: down to their basics, they are just patterns for structures
Speaker 2: you already use every day. An option or an I
Speaker 2: innumerable list. Those are containers. They represent values existing at
Speaker 2: an elevated level of abstraction.
Speaker 1: So let's demystify the functor first, which relies on a
Speaker 1: function called map. The map function reaches into your container,
Speaker 1: takes the inner value out, applies your regular business logic
Speaker 1: function to it, and then reboxes the new result back
Speaker 1: into a new container, and you never have to write
Speaker 1: an if statement to check if the value is actually there.
Speaker 2: If the option box happens to be in the nun state.
Speaker 2: If it's empty, Map is smart enough to just bypass
Speaker 2: your function entirely and return a new none. If it
Speaker 2: is in the sum state, it applies the function and
Speaker 2: wraps the result in a new sum. It completely abstracts
Speaker 2: away the boilerplate of checking for the absence of data.
Speaker 1: The book has an amazing analogy to explain map. Think
Speaker 1: of your aunt who absolutely loves baking apple pies.
Speaker 2: Okay, I love this one.
Speaker 3: You leave a.
Speaker 1: Basket of apples on her front porch. The basket itself
Speaker 1: is the option container. The apples are the concrete value
Speaker 1: inside her baking skills represent your business logic function. You
Speaker 1: leave the basket, She takes the apples out, bakes a pie,
Speaker 1: and puts the pie back into the basket. What started
Speaker 1: as an option of apples is now an option of pie.
Speaker 2: And how does she handle an empty basket?
Speaker 1: That's the brilliance of map. If you leave an empty
Speaker 1: basket of bandit yeah, yeah, none, she doesn't freak out,
Speaker 1: She doesn't throw a null reference exception or call you
Speaker 1: in a panic. She just smiles and leaves the empty
Speaker 1: basket right there on the board exactly. You don't have
Speaker 1: to explain to her what to do with empty space.
Speaker 1: The map process handles the empty state automatically.
Speaker 2: That analogy perfectly captures how a functor works, but we
Speaker 2: inevitably run into a more complex scenario. What happens if
Speaker 2: the function you are applying also returns an option container?
Speaker 3: Oh? I see?
Speaker 1: Like if I have an option of a strength from
Speaker 1: a database and I pass it to a parsing function
Speaker 1: that tries to turn it into an integer, that parsing
Speaker 1: function might fail, so it returns an option of an integer, right,
Speaker 1: and then I pass that integer to our smart constructor
Speaker 1: for age, which also returns an option of age.
Speaker 2: Precisely, if you use the standard map function to string
Speaker 2: those operations together, you have a structural problem.
Speaker 1: Let me think about how the generic types would stack up.
Speaker 1: Map always puts the result in a new box. So
Speaker 1: if my function returns an option age and Map puts
Speaker 1: that result in a box, I end up with an
Speaker 1: option containing option containing an age. Yeah, it's like Russian
Speaker 1: nesting dolls of day option age. That sounds impossible to
Speaker 1: work with.
Speaker 2: It is very clunky, and that exact problem nested containers
Speaker 2: is where monads and the bind function come to the rescue.
Speaker 3: Okay, so what does bind do.
Speaker 2: Bind does the exact same conditional execution as MAP, but
Speaker 2: it has a special power. It flattens the result. Instead
Speaker 2: of handing you nested boxes, Bind unwraps the inner box
Speaker 2: and hands you a single flat optionage.
Speaker 3: Oh wow.
Speaker 1: To adapt the ant analogy, it's like if the ant
Speaker 1: bakes the pie, but you put the pie into her
Speaker 1: own special pie basket. If you used map, you'd come
Speaker 1: back to find your porch basket containing her pie basket
Speaker 1: containing the.
Speaker 2: Pie, right, which is just silly.
Speaker 1: But Bind is the process where she realizes that's ridiculous,
Speaker 1: takes the pie out of her basket and just puts
Speaker 1: it directly into yours.
Speaker 3: You get one flat basket.
Speaker 2: That is a brilliant way to conceptualize flattening. So map
Speaker 2: is for applying regular functions and bind is for applying
Speaker 2: functions that themselves return Containerska.
Speaker 3: It makes sense.
Speaker 2: And finally there's four each which we use right at
Speaker 2: the very end of the pipeline. We use map and
Speaker 2: bind to run all our pure logics safely inside the containers,
Speaker 2: and then we use four each to safely execute our
Speaker 2: impure side effects like physically writing to a database.
Speaker 1: But it only executes if the data actually made it
Speaker 1: through the entire pipeline without turning into a nun exactly.
Speaker 1: So we have all the building blocks on the table.
Speaker 1: Now we have pure functions, honest signatures via smart constructors,
Speaker 1: option types to handle missing data, and map and bind
Speaker 1: to process logic without if statements. You do, how do
Speaker 1: we actually string all of this together to build a
Speaker 1: real end to end architecture.
Speaker 2: Well, we build functional workflows using function composition and method chaining.
Speaker 2: We're used to this in object oriented code, where you
Speaker 2: chain methods together with dot notation. The data flows linearly
Speaker 2: reading from left to right.
Speaker 1: Li inq is probably the best example of this workflow
Speaker 1: mindset that see sharp developers already use. Definitely, if you
Speaker 1: want to find the average earnings of the richest core
Speaker 1: tile of a population. You don't write nested for loops
Speaker 1: and temporary variables. You write a declarative chain you call
Speaker 1: order by descending then dot, take dot, select dot average.
Speaker 1: It's flat readable and defines the pipeline perfectly.
Speaker 2: Now, let's elevate that LA and Q mindset and apply
Speaker 2: it to a real server side scenario from the book.
Speaker 2: Dealing with the fictional bank of code Land.
Speaker 3: Okay, Bank of code Land.
Speaker 2: We receive a web request to process a make transfer
Speaker 2: command between two bank accounts.
Speaker 1: Right, and in a traditional imperiadive web controller, that code
Speaker 1: gets messy fast. You have an if statement to check
Speaker 1: if the request is null, then a trycatch block around
Speaker 1: the database lookup inside that. Another is statement checking if
Speaker 1: the account balance is high enough.
Speaker 2: Yeah. It creates this deeply nested arrow code that is
Speaker 2: really hard to scan.
Speaker 1: But by utilizing our functional building blocks, the entire web
Speaker 1: controller can be rewritten to read like a single flat sentence.
Speaker 2: It really is striking to look at.
Speaker 1: Translating the code into plain English. The pipeline flows exactly
Speaker 1: like this. You lift the raw transfer request into an
Speaker 1: option box. You map it through a function that normalizes
Speaker 1: the text, you filter it through a wear clause that
Speaker 1: validates the accounts, and if and only if it survives
Speaker 1: all those steps without turning into a hote none, you
Speaker 1: call for each to execute the side effect that physically
Speaker 1: books the transfer.
Speaker 3: In the ledger.
Speaker 2: Every step is isolated. You never have to manually check
Speaker 2: for nulls, and you never have to catch a validation
Speaker 2: exception because the option context handles the flow control for you.
Speaker 1: It is incredibly elegant. But when we look at the
Speaker 1: domain model for actually booking that transfer, things get a
Speaker 1: big controversial.
Speaker 2: Out the OP versus FP modeling.
Speaker 3: Yeah.
Speaker 1: In the book's comparison, the traditional object oriented account object
Speaker 1: has a debit method. You pass it an amount, it
Speaker 1: checks its own balance, it mutates its internal state to
Speaker 1: lower the balance, and it throws an exception if you
Speaker 1: don't have enough money.
Speaker 2: Which, as we've established, breaks all our rules. It relies
Speaker 2: on hidden state mutation and throwing an exception makes it
Speaker 2: an impure, dishonest signature.
Speaker 3: Right.
Speaker 1: But the functional replacement they propose strips the behavior completely
Speaker 1: out of the object. We get an account state object
Speaker 1: that is essentially just dumb immutable data with public properties. Yes,
Speaker 1: and the actual debit logic is moved to a pure
Speaker 1: static function that takes the old state as an argument
Speaker 1: and returns an option account state containing the new balance.
Speaker 1: I have to say I struggle with this. For decades
Speaker 1: we've been taught that OP is fundamentally about encapsulating data
Speaker 1: and behavior together inside objects. Splitting the account state.
Speaker 3: Data away from the debit logic feels like we are.
Speaker 1: Abandoning OOP and going backward to procedural programming from the
Speaker 1: nineteen seventies. Why is tearing encapsulation apart considered better?
Speaker 2: It is a very common reaction to feel like separating
Speaker 2: data and behavior is a regression, but in this context
Speaker 2: it's actually an elevation of your architecture. How So, in OOP,
Speaker 2: tightly binding data and behavior inside a single object often
Speaker 2: leads to hidden side effects and unpredictable state mutation over time,
Speaker 2: and as we saw with the sorting concurrency disaster, mutable
Speaker 2: state causes massive bugs when systems scale. By separating them,
Speaker 2: account state becomes a pristine, thread safe snapshot in time.
Speaker 2: It can never be corrupted by a background threat.
Speaker 3: Oh, I see, And the.
Speaker 2: Pure static debit function doesn't need tate anything. It simply
Speaker 2: evaluates the pristine snapshot runs the math and securely calculates
Speaker 2: the next step in the pipeline. It makes your workflows
Speaker 2: highly composable.
Speaker 1: Because you can pipe the output of debit directly into
Speaker 1: the next function without ever worrying that some internal property
Speaker 1: was secretly altered or that a hidden exception is about
Speaker 1: to blow up your call stack.
Speaker 2: Exactly so, it's not about regressing to procedural spaghetti code.
Speaker 2: It's about making the business logic mathematically predictable and completely
Speaker 2: testable in isolation.
Speaker 1: That makes a lot of sense. Functional programming in C
Speaker 1: Sharp isn't about throwing away the language features. It's about
Speaker 1: using them to build a highly pragmatic, incredibly resilient tool set.
Speaker 2: That's the perfect way to summarize it.
Speaker 1: Let's quickly recap the mission we set out on today.
Speaker 1: By treating our functions as data that can be passed around,
Speaker 1: by rigorously avoiding state mutation, and by utilizing honest structures
Speaker 1: like the option type, paired with map and bind, you
Speaker 1: can make your c shark code incredibly concise. Absolutely, you
Speaker 1: can almost entirely eliminate null reference exceptions because the compiler
Speaker 1: forces you to acknowledge missing data and most importantly, you
Speaker 1: make concurrency a breeze because immutable data simply does not
Speaker 1: suffer from raise conditions.
Speaker 2: It definitely requires a paradigm shift in how you view
Speaker 2: dataflow and state mutation, but the payoff in reliability and
Speaker 2: reduce debugging time is immense.
Speaker 1: To wrap up, I want to leave you with a
Speaker 1: provocative thought to molover. If we can elegantly represent the
Speaker 1: total absence of data using just the option type Wait,
Speaker 1: I mean think about that. And if we can completely
Speaker 1: remove nested if statements from our web controllers using map
Speaker 1: and wear pipelines, what other traditional imperative control structures could
Speaker 1: we entirely replace?
Speaker 2: That's a great question.
Speaker 1: Could functional wrappers entirely eliminate the need for trycatch blocks
Speaker 1: in your application? Could they replace traditional wile loops? How
Speaker 1: much shorter and how much more bulletproof could your legacy
Speaker 1: codebase become? If you started thinking impure functions today, maybe
Speaker 1: it is finally time to stop the frantic page slipping
Speaker 1: in your software and just enjoy the book.