I vibe with this, but no matter how much functional propaganda I consume, I do not understand how it's possible to write a full program without state. Let alone something like a game.
@@halotroop2288you pass the state around as an argument. So in a game you'd be passing the game state through your game loop and update it accordingly. :)
@@adora_was_taken it's a different way to solve problems. Of course you could mimic it in other paradigms. It's similar to Data Oriented Programming, which is fairly popular in game dev too.
Code Aesthetic: Makes a whole video about being a "never nester" Also Code Aesthetic: 4:10 puts an entire function's logic inside an if statement instead of using a return.
Early returns are not there in most functional languages. So it's not common to use them. The same is true for local variables. Creating them usually requires a `let ... in` construct so they are also avoided
Judging from their second channel and their website, I think they've been on a journey to rediscover non-OOP and computer science since the never nester thing. Also, it's not a bad thing to change your mind.
My guess is it was just for ease of understanding. He's showing how the condition moves down to the recursive solution so any change there is not necessary for the point. Would've been nice to see him do the solution in the video and simplify it to flatten the if statement.
I learned functional programming via F# and Clojure, and came away with a rather impure lesson: You only need a SUBSET of pure functional concepts to write great software. I've found that in most languages, you get 80% of the benefit from 20% of the tools: Referential transparency via expression-based programming; no state or local-only state (avoid global state or state passing at all costs); chained stateless transformations, via things like universal function call syntax, pipe operators, map, filter, reduce, and comprehensions; a strong preference for passing around and transforming simple data structures, such as records/structs; liberal use of tagged unions/variants to create exhaustive conditionals that make illegal states unrepresentable; and making functions short and sweet, which allows for minimal nesting and easy naming. Your program then becomes a pyramid of calls to what is essentially a DSL.
Hard agree. I’ve been all the way down the FP rabbit hole and back again. My takeaway is that the biggest bang for your buck is functional style state management. Usually there is a framework that does this for you. Use it.
Agreed, using imperative code inside the local scope of a function is a good trade off to maintain simplicity and performance, so long as your exported function maintains referential integrity
I somewhat agree. But then, what are examples of stuff you consider to be functional but out of that subset you mention? To me, you summed up what functional means, and I don't get why you would call that an "impure lesson" (other than for the local-only state). 🤔
"Your program then becomes a pyramid of calls to what is essentially a DSL." *Lisp curse intensifies* Seriously look up the lisp curse by Winestock. The problem is that in a team of 5 people, they'll be able to come up with at least 6 different DSLs for the feature set. And which one is the most correct is usually dependent on future unknown expansion of feature set.
Lambda calculus (which most of the FP is based on) is not just about purity and statelessness of functions and converting loops to tail recursion calls. It ensures transparency of the functions. The entire tree of terms is always available. Which means, the compiler can reduce expression trees, sees data access patterns, can replace operations and data structures with more efficient ones, eliminate effectively dead code, etc., and do so across the function calls. These techniques are used even in imperative language translators (e.g. GCC and Clang), but they are tied by unpredictably mutable state.
But full immutability also makes a lot of things harder. I implemented a compiler in Haskell once. It was not fun, at all. Want to modify a syntax tree while type checking it? Have fun rebuilding the entire tree, which is both error-prone (accidentally changing left-hand side and right-hand side of binary operators or using older (not type checked) nodes) and makes code harder to understand. Or you can use the same syntax tree and make the same checks every time (like dealing with == operator for arrays differently). Or maybe there is some fancy way of doing it with a shit ton of abstraction that I couldn't figure out because I don't have a PhD in category theory and computer science. Ironically, Haskell taught me the beauty of procedural programming.
@@AndreiGeorgescu-j9p the comparison between mathematics and programming is weird, because they are mostly unrelated in this context. Even if you want to go along with it, mathematicians use a mix of mathematical notation and kind-of-imperative natural language, 'cause explaining everything using only maths and logic would be unreadable. Maybe functional languages are good for toy compilers (that probably use parser and lexer generators), but any serious compiler like gcc or nodejs uses procedural/imperative style, with hand written parsers. Most of the widely used languages combine procedural or imperative with other paradigms. It's weird to say that procedural style is garbage when Haskell literally reinvents it. What is a state monad? It's Haskell reinventing mutability. What is IO monad? Just statements in imperative languages. Why do you need those if functions are so good? Cause you can't program in a convenient way using only functions without adding a shit ton of abstractions and concepts.
To be fair, these optimizations also happen in imperative languages, especially if the compiler does effect analysis. Of course, guarantees are more reliable than compiler inference. This is not to say functional languages always yield inherently more performant programs, that should be left up to benchmarks in practical applications.
@@AndreiGeorgescu-j9p you completely missed the point about mathematics. Are math articles/books written in math notation only? No, because it's unreadable. Do teachers throw pure formulas at students? No, they explain it with words + show some formulas/definitions. At no point did I claim that mathematical language is imperative. "How to prove X? Show, this, this and that", that's pretty imperative. Whatever, this discussion is pointless, you seem to be one of those pure functional programming zealots.
@@AndreiGeorgescu-j9p "It definitely doesn't make things harder, that's why mathematicians use "immutability", aka real variables. No mathematician is writing "imperative" math." this is factually incorrect (and a misunderstanding of mathematics). If I define the variable x = 1 in one context, and x = 3 in another context, my x is not universally immutable; it is contextually immutable and it may be a different value in a different state. Not to mention that there is an entire category of mathematical functions that are iterative over some mutating variable en.wikipedia.org/wiki/Series_(mathematics)
The functional emphasis on reducing state and minimising side effects also, in my opinion, greatly helps with reducing code complexity in the way described in "Philosophy of Software Development". In that book, he emphasises creating modules or functions with deep functionality, and simple interfaces. If your functions are pure and have no side effects, it simplifies the actual interface and avoids information leakage - you only have to know what the function explicitly does, what its parameters are, and what it returns. The implementation is entirely irrelevant. I've definitely edited some people's code where I've been confused by bugs, only to find that a function somewhere is sneakily mutating the state of the code when I'm not aware of it. Having a guarantee that a function doesn't do such a thing is really useful when trying to quickly understand and use a system. I think something like naming functions which are pure as this_is_a_function_P and ones which have side effects as this_is_another_function_Imp would potentially help, provided people do stick to those rules. Just my two cents!
With JavaScript, you can customize ESLint to give you errors or warnings when a function modifies its inputs. You can customize the rule to not report errors if your parameters are written with a specific pattern. I configured it so that if you intend for an input to be modified (like when using an array.forEach()), your parameter name should start with "mut_". We have eliminated non-pure functions thanks to this
that should be something that the compiler enforces, for example in swift when you want to change some value of the containing struct inside of a method, that method has to be explicitly marked with the mutating keyword, otherwise you will get an error
@@voidmind that fixes part of the problem, though it still doesn't stop you having some class method foo(int a) {b = a; self.c = 20; return b}, so doesn't guarantee pure functions as side effects are still possible. However, that sounds like a good approach to do this in a language which doesn't enforce these kinds of rules
@@LosFarmosCTL yep, or rust for example has all data as const by default, which is a much better way than the standard mutable by default approach, though I doubt many other languages will adopt it sadly
Important to note that in JavaScript, Array.sort() sorts the array in place. However, ECMAScript 2023 introduced a new non-destructive method for sorting an array, Array.toSorted(). PS. It also introduced Array.toReversed() and Array.toSpliced(), non-destructive alternatives to Array.reverse() and Array.splice().
now if only they'd give us summed, grouped, unique, and partitioned..... yes, you can do literally all of that in `reduce` but reduce is notoriously easy to write and hard to read. also, they have you put the initial value _after_ the function, which is just sacrilege.
@@Bliss467 the initial value after the function is correct because in bind-order the function is what you would bind first when specialising it under partial operation
Can we petition to introduce Array.toMapped, Array.toFiltered, etc. that mutates the original array to just really destroy the souls of new developers?
@@cadekachelmeier7251 I think those functions are both ridiculous though. Array.prototype.toMapped = function(transform){this.forEach((e,i,a,)=>{a[i]=transform(e,i,a);});}; // this just uses for each! toFiltered would either be inefficient OR take up extra memory. inefficient way: Array.prototype.toFiltered = function(condition){ // yes, this for loop is horrendous for(let i = 0, c; i < this.length; c?(i++) :(this.splice(i,1))) c=condition(this[i])); return this; }; extra memory way: Array.prototype.toFiltered = function(condition){ const f = this.filter(condition); // apply is used because ...ellipsis is slow f.unshift(0); f.unshift(this.length]); this.splice.apply(args); return this; };
This pipeline approach is one of the things I love about Rust. In JS, the array methods operate on the entire array at each step, whereas Rust iterators work on a single item from the iterator at a time across the entire pipeline.
That's one thing I'm always a little annoyed about in JS, the functional methods are *only* defined on arrays, not iterators. Which sucks sometimes. Apparently they are in development on iterators but its not implemented everywhere, including node.js, so like... not really usable
Also, JS copies arrays in ram to work on them (which sucks even more if you chain multiple of those functions together), while Rust "resolves" your chain of functions at compile time. It's part of Rusts zero-cost abstraction philosophy. Rust is awesome.
Recursive functions can be scary because it can lead to very long call stacks and stack overflows. I recently learned about tail calls which some languages can use to avoid this. Essentially, if the last line run in a function is another function, the rest of the resources from the completed function can be freed up. I learned about this from Lua, but it looks like there is some compiler support in C++ as well
Scheme's spec mandates that implementations guarantee tail recursion. WASM has a tail recursion extension as well. JavaScript was supposed to get guaranteed tail calls, but it was veto'd by Apple and Google IIRC. A lot of compilers (for example Rust's) will do TCO or tail call optimization, but this isn't guaranteed, so I personally find it annoying to rely on. I don't want to write higher level code and then check the assembly to see if it's actually optimized the way I want it. Of course it's basically guaranteed to be available in functional languages like Haskell, OCaml, Elixir, etc.
Even though it works, it really feels like a hack. What if the compiler accidently does not recognize this for some reason? Then you have unexpected behavior that might also be hard find out about.
@@joranmulderij once you get used to it, it's simple enough to reason about. for example, if i want to write a non-tail recursive factorial, it will look like this: func factorial(int n) { if n
As someone who is trying to learn programming, and has only so far found OO educational materials, I have had a lot of trouble trying to comprehend what a functional approach would look like. I finally get it. Thank you so much! I feel like I have the power to ask better questions now, and keep moving forward.
it looks like a lot of code that noone can read. Because at the end of the day, at the level of the CPU and even before function and data is the same thing. In my opinion it is ok to concatenate functions in a left to right syntax, but having the ARGUMENT and not the result of a function be another fucntion (so kind of right to left) is just making your code unnecessarily hard to read
As an early career developer without a CS or mathematics degree, I really appreciate how you explain things and your delivery. I would love for you to do a series of deep dives into various principles and paradigms. Whatever you do I'm sure it'll be great and I'll be here for it. Happy holidays, stay safe 🎄
This was a really good execution of a 3b1b-style "rediscovering the fundamentals from scratch" journey. Also, I have a sudden urge to take pictures of my receipts and upload them somewhere, but I'm sure it's completely unrelated
The timing on this video is perfect. I've been on an fp video binge the last few weeks and you have a way of visually explaining things that I've always appreciated.
This video was a really great overview! Since you're on an fp video binge, maybe you'd be interested in the series I'm starting (check out the channel page). I think there's also a lot of value to understanding some of the math concepts... NOT because smart words make you smart, but because the smart words are actually fairly simple concepts that are quite universal, so knowing them helps you see patterns in problems, which you can then solve more simply
@@kylewollman2239 lovely, thanks for watching! I hope you enjoy the rest. And please leave me any feedback on the format/pace/content, so I can tweak the series to be most fun and helpful as possible!
Thank you very much! I've only ever learned OOP, and have been struggling to properly conceptualise functional programming for months, but this video made so much sense. Ive been seeing those snippets of functional programming code at work, it is good to be able to better understand the method and thinking behind them.
Take note that doing FP in JS may induce a steep performance penalty, for you are allocating a new array at every chained step and copying the data. And in most cases the runtime cannot optimise this because the closures have no pureness guarantee (i.e. may have side effects). If you want the true functional experience, learn SML or Haskell. If you want the practical functional experience and still keep your sanity (i.e. the choice to opt-in/out at will), learn Rust.
I'm not sure the performance penalty is at all something to worry about in web dev with JS. What data collection are you working with on a web app directly, that is large enough for performance to take a noticeable hit? Even if we make some ridiculous large shopping cart or whatever, with 1000 of different items added, it would still take a FP approach less than the blink of an eye to process - on an old and bad PC! I think you're prematurely optimizing for something that would never be an actual measurable difference. In turn, you're losing the FP way which can be easier to reason about and way more concise. (oh yeah, I said it won't even be noticeable in an old and bad pc with an unreasonably artificial huge shopping cart. If it's a standard budget modernish laptop then it even make less sense)
Imagine if all developers thought like that, software would be even slower. I'm a huge FP fan, but I won't use it for languages that don't have compiler support to make it fast (e.g in-place opportunistic mutation). "Premature optimization".... lol, people really need to stop using that term.@@marouanelhaddad8632
Fantastic video! I love FP and dislike the F-Bro lingo as well. My gross oversimplification of FP to regular programmers has always come down to: 1. Write more pure functions. 2. Use more immutable variables. Nearly all the complexities of FP can be derived from making these two happen, yet MANY good, simple patterns in any language also stem from these.
Monads/applicative functors are going to be the next FP feature to appear in mainstream languages I think. The usefulness of piping a transformation that can fail/have multiple outcomes is just so great compared to infinite 'if' checks, scary runtime exceptions, or unpacking multiple lists only to map over them immediately after. Kotlin already has a "access member if not null" operator, Rust is similar with its Result and Option datatypes.
I implemented Result/Option in C# using a Nick Chapsas video as reference, and they've been a godsend for cutting down on null exceptions and all the guard clauses to prevent them. If only they were built-in, I'm sure they'd be way better than what I cobbled together haha. Definitely not as nice as Rust, where they're core language featues, but way better than nullable or relying on null checks.
I really hope so! I think the Effect library will do a lot to bring those to TS in a very usable way. I was using fp-ts before, but found it clunky. Effect might make it all usable enough to actually get popular 🤞
@@TheMasonX23actually, neither Result nor Option are core language features; they're perfectly standard data types that you could implement equivalently in user code (the difference is that because they're part of the core _library_ they're ubiquitous in APIs, which wouldn't be the case for a user code version)
If you're going to make such a good video on Functional programming, you gotta mention Iterators and Lazy evaluation. I'm not a functional bro, but I know they love those things. They're the reason why in javascript arr.filter().map().take()/reduce() is O(3n) and in rust its O(n). No, we're not dropping the constant here, if you get carried away with "functional style" in js, your app will really show its lack of iterators. For those unaware, iterators will basically turn our data pipeline on its head where instead of looping the array 3 times for each transformation, we're going to process 1 item at a time and apply all transformations sequentially on it. And this IMO is key for marketing someone to functional programming, because otherwise functional just looks like slower less optimized code. What's funny is that rust has iterators, but functional bros would get angry if you called rust a functional language. Some even get mad if you call Ocaml functional, since ocaml has for/while loops.
btw, Ramdajs is a library that is well known for two things. 1. Functional style, lodash-like helper functions and 2. terrible performance, due to lack of iterators. So bad that it spawned another library that sold itself as "ramda but smaller and with better performance" and one of the core things they did to speed it up was add iterators (among other things).
As a scientific programmer (in chemistry) who uses Julia intensively after bad experiences with C: I feel like to first order, state is the enemy of scientific programmers (more generally, when state is not relevant to a problem, it should be abstracted away as much as possible). Functional programming maps very naturally onto a lot of what we do, but a lot of the trouble people around me had was the degree to which we were taught we had to manage state when we really didn't.
I love the visualisation of the functions! Btw, you would be super cool if you did a video about array programming! Like with APL or BQN etc. Those are the most aesthetic language in my opinion and it would blow a lot of peoples mind if they saw that video!
I mean he kind of set it up. We now need a video on functional combinators, monads and then we can slowly start to swallow the array pill and have our 20 lines of BQN code that form an Excel clone, a web server, the entirety of Facebook, a missile guidance system, a hospital management software and a smart thermostat all in one go.
you say that like array programming is the next step, but i have my doubts. it seems to lack the composability of functional programming by imposing more arbitrary limitations. from what i remember, there is also a lack of higher order functions in array programming
@@xybersurfer it’s funny that you say it lacks composability when that is one of the strongest features. Code_report (TH-cam channel) has some good videos showing it 😁 also comparing with Haskell. They also have operators (in APL speak) or modifiers (in BQN speak) which are sort of higher order functions, but BQN (the language I write in (I have some videos on my account if you’re interested)) also supports higher order functions in the functional sense. And the restriction (constraining to arrays) isn’t arbitrary, it gives a lot of power and expressivity exactly due to limiting the baseline to a bedrock foundation and building from there using the most flexible and essential functions and making them compostable in every way you’d want. I personally really love them, and if you get your mind into array thinking they’re super expressive, powerful and fun to work with
@@davidzwitser oh then perhaps these limitations are specific to the variant of array programming that i used. i looked at the video "I ❤ BQN and Haskell" by code_report. it's shorter than Haskell, but it looks like write-only code (similar to APL). to me it would make more sense if this form of polymorphism was added to Haskell
This is a nice video! I'm extremely a fan of declarative paradigms, but it's quite hard to get others to understand why it's cool. Functional is just one step there. Stuff like logic programming is even more intriguing, though haven't have much opportunity to get into it yet. At least SQL is known by everyone and IMO one of the best arguments to be made for people dissing on declarative - imagine writing this complex SQL query manually! I think that should help people understand the value of declarative too.
I love declarative stuff as well. I do have to say that it is very often not the best way to solve a problem, but in cases where it does work, it is often by far the best solution.
@@joranmulderij Dunno about the domain you work it, but for me it's almost seems the best, at least as bulk of code. As video author said yeah you need to wrap it around on edge IO cases, but bulk imo very often can be done that way. At least in web dev and shell pipelines. Also a lot of times there it seems to not be the case IMO existing surrounding code is the reason why and not that it would not work. Just some imperative stuff is so proliferate that it's very hard to do work with it. Imo in future it will be declarative almost always everywhere, at its way more composable, and also giving way more leeway for compilers/systems to decide on how to execute (think SQL changing execution plans automatically based on what it knows about indexes/data stats and what not). For me it seems like a tragedy that imperative was allowed to poison software development for so long. But then, as you can see I have strong opinions :D
@@morphles The main downside to declarative programming is that it is way easier to get yourself stuck. You really need to "design" the interface. But once that is done and your use case fits tightly without what the declarative system was designed for, then the DX is amazing. I'm currently writing a declarative nodal reactive state management library with two way data flow, which would allow you to have a full-stack reactive system without getting your hands dirty with caching mechanisms etc.
I learned to love FP after I gave up to understand *what* a monad is and started just using what the language offered me. Rust's Result type is such a cool concept! *Months later:* **Wait, that's a Monad**!!? Anyways, that literally doesn't change anything, back to using the cool Result type! Ignore the occult name we give those fellas, just look at *how* they work and you'll find it absolutely fun and game changing! Don't search *what are monads for christ sake*. Search *how to use Rust Result type? When and why is it useful?* This will forever change your learning curve to a satisfactory realization that yeah, I guess those Monads are cool, I use them all the time! What is a monad? No idea! I can show you an example on how some are cool though, wanna see?
The currying explanation finally made it click for me, good video mate! Also even if a FP lang is not truly "pure" having immutability and the semantics that brings, it gets a lot easier to reason about code.
That's cool and stuff, but this example only works because you only use functions that operate on lists and use fluent pattern. The good thing of functional programming is that you can pipe anything as long as output of previous matches input of the next. Absense of nulls and exceptions and adts are also nice. Monads and all that wizardry is just a consequence of using it all together
And it also works because every function being piped doesn't require any additional information beside what is in the list. As soon as you need to propagate some information, you end with a lot of useless arguments that are just being passed around. Like what if you want to know the index of filtered element in original array? You now need to create an array of pairs (index, element), and every function needs to accept that pair and propagate that index through every call. That is error-prone (what if you accidentally pass an incorrect number somewhere?) and clutters the functions (that index is not used for anything in most functions).
@@clawsie5543 you're doing it wrong. You don't need to flatten everything down to pass it along, just have that part of the chain as a closure over the original index and the code that needs to reference it, just does :shrug:
My job had a massive ball of state that was a slow, difficult to navigate mess. Over two grueling months, I replaced a massive section of the base with pure functions. Nothing crazy or convoluted. Just functions that transformed the data the app was required to transform. The result was seamless parallelism with Java's parallelStream( ) API, 100% test coverage (for that section), and total eradication of classes like "AbstractParserFactoryBuilder". I was a functional fanboy. When I finally walked the walk, I became a whole ass functional disciple.
TBH Functional Programming is very much a means of carrying over principles from analog data processing (such as control theory and analog computing) into the world of digital computing. Filter design, block diagrams & transfer functions, feedback & feedforward, hysteresis, etc. are all from the world that existed before digital took off.
There are actually multiple hardware description languages, used to describe circuits to imprint on a silicon die or an ASIC, that are based on functional programming or simply are functional programming languages. More traditional HDLs like Verilog and VHDL are almost _weirdly_ imperative for something intended to model essentially a pipeline of gates and circuits wired directly into other gates and circuits.
As a JS developer, the best extract I got from functional programming is being mindful of how I funnel state changes in my code. Pure functions help a lot, as well as understanding the difference between a shallow and deep copy. And yes, I'll favour map, forEach, reduce, along with most array methods, over imperatively written loops.
Be aware that in javascript, many of these operations will have significantly higher CPU and memory overheads vs more traditional approaches, particularly with how much JS loves to mutate and copy arrays under the hood. This is not to say you shouldn't use them, composing reliable and easy-to-read functions is an extremely powerful way to write easy to parse and easy to debug code, but it should be applied carefully and checked for performance when used in performance-critical areas of code. As someone who writes a lot of performance-critical JS (irony, I know), I generally avoid them for that reason, but they have their place.
One of the biggest benefits of keeping things pure is that it's easy to write unit tests. I am an "FP bro" and must say that your code was a little bit complex. I usually split the pipeline steps into variable assignments to make it more self documenting, unless its steps are very simple.
Totally! Testing is not a way to catch bugs, it's also a great way to force better code design. If something is easy to test, it's probably written pretty well. And FP is great for that since everything is simple input-output. Mocking, particularly, becomes a non-issue
@@LetsGetIntoItMedia Agree 100%. Just like Rust gives a guarantee of memory safety, pure functions give a sort of guarantee that the function does one thing only. Regardless of the logic that produces the transformation. One can even take it to the next level by writing property based tests. I find writing property test very difficult, but for the more capable and skilled programmers out there it can give extreme code coverage.
Ohhh yeah, I find property based testing really difficult in practice too. Because then it adds a bunch of complexity in generating the random inputs, and making sure that you cover all the possible edge cases. That, to me, is not worth it. I'd rather just keep functions small, and have tests be very very hardcoded. Think through your edge cases and manually write them all out. And if a production issue comes up, add that as a new unit test to catch that regression in the future. One of my teammates once said, "tests are only worth it if they save you more time/pain in bugfixes, production issues, middle-of-the-night emergencies, and reputation than the amount of work required to write them". I think property testing is over that threshold.
@@LetsGetIntoItMedia Good point. What do you feel about using a debugger? Personally, I'm so bad at programming that I actually spend 80% of the time stepping through the failing tests in the debugger until I find the root of the issue. I used to hate the debugger, but now I'm starting to become comfortable with it.
There is no other way 😁 when I was first introduced to a debugger, it blew my mind. (But I'm about to contradict myself. There are definitely other ways, and often necessary...) But that was in Java, where stack traces are much nicer. In Javascript, there are so many anonymous functions and library code that it makes it much trickier. Particularly in pipelines, since there's not an obvious place to even place a breakpoint. I end up using pipe(fn, fn, a => { debugger; return a; }, fn) a lot. The debugger is still a good tool, but Javascript makes it tricky. Next best thing is to keep trying to isolate pieces of code and run them in smaller test cases. Unit testing is key.
Honestly for me the sweet spot has always been a mix of functional and OOP. In another word, I would write immutable functions that don't have any side-effects. However if it comes to function state or currying. I would never write a function that returns another function, cauz at that point you're basically creating the same thing as a class, that has an immutable state (read-only properties). And between a class and currying, I would choose a class over a binding function for readability, visibility and testability concerns. As a matter of fact, even a class should always have pure and functional methods imo. And this is what I meant with mixing. That's why there's no clean code where you use only one style. And you can't glamorize only OOP or only functional. I found mixing to be the best.
Functional bro here. That's probably my favourite "very beginner functional stuff video" now. I had ~10y of Dart+JS+TS before turning into (Pure) Functional Bro writing stuff in Haskell. Most of the videos about functional programming, at some point, make me think "this guy doesn't know shite and talks nonsense". However this is video is nearly perfect in this regard. However the video only covers only tip of the iceberg. Lambdas and map/filter/reduce are pretty much everywhere. Some languages are even getting algebraic datatypes (just calling them differently). Some languages actually have stuff that are almost monads tho hardcoded for specific cases (Promises and nullable types, yeah). These are cool and all but it's all a bit of shoehorning - functional paradigm works poorly in essentially imperative languages the deeper you go with functional stuff. Syntax is a bit awkward because there is no proper function composition or currying. You have to be careful with performance because in absence of purity and lazy computations you'll can end up reallocation same array bajillion times. You can bork the stack because you don't have TCO or it's not guaranteed to kick in. In the end it's almost only syntax sugar with awkward syntax at the end. Unfortunately most mainstream languages won't ever get the coolest parts of the iceberg like referential transparancy, advanced typesystems and stuff that depends on that. Just because you have to design the language for it from the beginning. What I'm trying to say, I guess, is: don't judge functional programming and functional bros (except the snobby smug ones - judge them to hell) right away for "impractical math nonsense" from your essentially imperative language point of view. F bros are using totally different language.
Good point about the almost monads. Iterators, streams, option/result types, etc. - eventually people will notice the common pattern and realise they've been using monads all this time :)
The song name in the outro music is called: Novembers - Just Ripe (Album is called Pour Me a Cup of That) I don't like the idea of paywalling the song names fam
@@AndreiGeorgescu-j9p Get off your high-horse, and stop spamming the same copy & pasted reply. Have fun with your pseudo-intellectual (and possibly imaginary?) job as well. Jesus...
Prof. who teaches Java here, and I have fallen in love with streams. Kotlin in particular has some absolutely gorgeous lambda based stuff in their stdlib.
Functional bro here! Guilty as charged. You're totally right... The concepts aren't actually that complex (a Monad is basically something that has a map() and flatMap(), and you can arrive at the need for that with a few simple code examples), *but* the words can be used to gatekeep or show off your smartness Also, the focus is often elegance for elegance's sake, rather than usefulness, simplicity, composability, safety, testability, etc. Though it is, in a way, the elegant foundations in math that allow FP patterns to get so powerful Also, I do think there's value in the words. It would be extremely difficult for society to function if we never put labels on things... how could we ever succinctly talk about anything without terms for concepts? Learning the FP words are useful not because knowing definitions is useful, but because they help you see those patterns in a problem space and then go r you immediate strategies for breaking them down into smaller bits and solving them concisely
@@AndreiGeorgescu-j9p Lol what a big non-response from you. Not only were you unnecessarily condescending, you never even addressed what he didn't understand about monads. You never addressed the elegance for elegance's sake argument, and then spent the rest of it inflating your own self-ego about being using functional programming. Then you all wonder why people don't take functional programming seriously. @LetsGetIntoItMedia I think the responder is a perfect example of why functional programming has a hard time making traction in comparison to oop when it comes to marketing it's benefits. A lof of of the folks trying to push it, spend way more time focusing on impractical simple examples that aren't of much help to anyone building applications, and seem to just use it as a way to flex how smart or clever they are.
Hmm yeah, I had a lot of thoughts to that response (which I'm not seeing anymore here, but saw the notification). My take is that I actually agree with a lot of the points that person made, but just absolutely NOT the tone or extremity. I totally agree that engineers *should* be into math, as they are literally writing computer math. But people come from all sorts of backgrounds, and that doesn't necessarily include a love for math. It might just be a love for creating stuff from more of a design perspective, or just a plain need for a job. And that's good, because we get a variety of perspectives and experiences in the field. People often *don't* think of math as something enjoyable, or realize how much code is like math. That's not a problem, that's an *opportunity* to share a new wonderful perspective. I also agree that FP isn't as off-putting or "gatekeepy" as the stereotypes suggest, but that's because I'm a math nerd on the side. But fact of the matter is, it *is* a stereotype, which means many people think it is unapproachable, and therefore by definition it *is* unapproachable. If most people agree that a balloon is red, then it probably *is* red. There is jargon in OO as well, that's for sure. And jargon isn't a bad thing. It's a way to capture a whole concept in a single word. So if everyone agrees on its definition, it makes communication much quicker and more efficient. *But* that's why, when talking to people who don't know those words, you have to do a good job explaining them. The problem is, based on the general sentiment, that people *don't* do a good job explaining the jargon, which feels like gatekeeping. Also, re: my definition of a monad, of course it's simplified. There are monad laws and all that, but that's exactly the stuff that turns people off. The average person doesn't need to know the exact precise definition of a monad, just some practical examples of how they can help simplify problems. Whew... I wasn't sure if I wanted to respond to that message, but I decided to make my perspective known. There's an *opportunity* to make the community better by *proving those stereotypes wrong*
This is absolutely hands down the best explanation I’ve ever seen on functional programming. I’m only a few minutes in and I’m just so blown away I had to comment. Definitely the best visualization of what’s going on, this is literally so extremely helpful for understanding this concept which I’ve been struggling to start to understand. Thank you so much, you make beautiful and helpful videos!
The point of functional programming is not to "have no state" it's just about how to effectively manage that state, this is the whole point of a monad!
The problem with functional programming in a language like JavaScript is that the JS runtime does not optimize functional techniques. Languages like F#, LISP, Scala, etc. can eliminate stack overflows and sometimes even do something called loop fusion where, in your pipeline example, all operations happen in a single loop instead of looping multiple times which is always the case in JS.
@@SogMosee What you are looking for is called an "unfold" or more generally an "anamorphism". It's type looks like: const unfold = (fn: (state: B) => [A, B] | null, initialState: B): [A] => { ... } and you would use it like: const results = unfold(nextToken => { if (!nextToken) return null; const { data, nextToken } = callApi(nextToken); return [ data, nextToken ]; }, ""); The implementation is left to the reader, turns out "unfold" is a pretty useful higher order combinator that pops up everywhere. Obviously it's not a javascript builtin and obviously we don't do promises here etc but yeah if given a toolbox of higher order functions, we usually don't need to recurse.
I love monads because they are impossible to understand theoretically, but take a half second to understand what it's doing when you actually see one even if you have no clue how monads are supposed to work.
If you're already writing a bunch of procedural code, and using all your language features, you should already be intuitively doing all this (If you've been coding long enough anyway). I've noticed that LLMs almost never give you functional code unless you're super specific in asking for it, and even then they will often mess up, and go back to managing state. This is because most people aren't optimizing, and so most the training data isn't optimized but I digress. The video was beautiful.
Absolutely stunning and I was quite amazed by the clarity of this video. As a total beginner to functional programming (with years of experience in OOP and recursion), I find it crispy clear with the visuals that you created. Thanks again for this work.
in the context of software development, mutable states are allowed if they are local to the functions. And vois la, just like that, loops are back in business, and you don't need to rely on recursion you might blow up your callstack if you are looping of a large dataset
not a functional bro myself, but i fell most of you are missing the most brilliant thing about FP: code-as-data. In a way, this is what every monad ever written is about, have your code broken in small steps that declare what you want to do and not how you want do it. Taking react as an example, component functions aren't just plain JS functions, they're monads! Components can do one of those things at a time: use a hook, run synchronous code, and return a JSX tree. those 3 kind of smaller steps are chained together in a plain js function to declare what your component do. ReactDOM.render() and react testing library's render() take that same data (your component) and interpret it in different ways. Migrating from stack to fiber was just a react dom update, because the only thing that had to change was the render method, data stayed the same. With react the same monadic interface (mixing hooks, sync code and JSX) can be used to render into the dom, render native apps, program embedded systems, create music, video and a lot of stuff. that's way monads are nice. And you don't need to go full-haskell-mode to benefit from that :D
The issue with stateless programming is that in real world objects _exist_ and they _have_ state. And most of domain specialists and product owners which you eventually communicate with, they use human language, and this language uses objects and states. This immediately leads to your common mental model of domain logic would have stateful objects. And then you would try to map stateful model on stateless paradigm. And then 99% of you start writing unreadable code.
One issue is that many of the more well-known functional languages are a pain to get into. Scheme is easy to pick up at first, but it quickly becomes challenging to tackle problems that are a breeze in other languages. Haskell is wonderful, if you can ever get it to compile. Some of the newer functional languages, though, like Clojure and Elixir, make FP much easier to get into. It is probably true that the most useful languages take some functional features (e.g. Rust), but it's good to have some experience with functional programming so you know when it's useful to bust out the maps, filters, and reduces.
lmao I always assumed functional programming was terrifying so hadn't looked into it further but it's just recursion and lambdas??? That's crazy. I'll have to look into it more. Thanks for the video!
I'm gonna get in trouble for this but functional programming has always seemed to me like when this guy showed be how he draws in Excel and I'm like, hey, that's cool. Only the functional programming people want to do it in production.
I've absolutely loved LINQ in C# for years, but only after learning Rust and more about FP in general, and I realize now that it was the use of higher order functions and FP paradigms that I enjoy so much. Also, I was blown away be the realization that Tasks are monads that I've been using this whole time...
Bad marketing for different paradigms is rampant across the programming discipline. At this point, I actively ignore its propenents until I've had a chance to learn more about it. Ironically, they suck the fun out of it for fresh newcomers.
One huge benefit I've seen in my own code by adopting functional principles is that it reduces your time complexity. Even in the example given, the functional pipeline breaks a O(n^2) down to an O(n). It's really easy to slap another nested for loop into something, but when thinking in terms of transforming pipelines there isn't nested code just one function after another. I work on an internal data viz platform for my work, and I've found in the frontend trying to rearrange data from the backend to be easier to display is a lot easier when using this style of programming. RxJS that comes with Angular enforces that while making your app "async" (I pipe user inputs through RxJS and have tried to make it more functional from there). Also using MongoDB on the backend with it's appropriately named "pipelines" feature is all functional transformation to make views and reports of data. It's amazing to see a full stack app where tons of functional programming can be used.
Certain things have many excited proponents that can't explain "why it's good" to anyone but each other. (Like, in my opinion: IOC, DI frameworks, fluent syntax.) I find most of these aren't worth using in pure form most of the time. However, my takeaway from functional programming class was that I felt extremely certain how my program behaves. After I got over the big mind flip! It's data processing didn't stick in my mind.
You are basically using IOC when working with frameworks. Let's say you have a game engine. Basically you just put your code in custom class and implement a couple of callback methods eg. Init() and Tick() and these methods will be called by the framework itself. It's good because you shouldn't care about who is calling these methods, what is the right order and preconditions, framework just doing it for you. Why is it good? Because you can abstract yourself from some things and focus on your features, which is basically why people use frameworks. DI containers are essentialy a mechanism for constructing your objects and providing dependencies. You can sure do that manually in composition root, where you build all your objects in specific order so all constructors can receive valid dependencies, and objects that created internally have their dependencies too, so it gets quite tedious and not very productive. With DI framework you can just define that "Foo is provided for IFoo as singleton" or "Bar is provided for IBar as new instance each time". Then if you want to add new class, you just write its code as usual, list all dependencies in constructor, then you bind it in container and it will resolve automatically providing all dependencies for you. Want to add new dependency? Just add it to constructor and it will just work. Specifically in games if you want to create new object from factory, factory don't need to know all the dependencies required for an object, DI container can provide all the dependencies for you. Isn't that good?
All of those patterns are FP in disguise. Inverson of control is just making use of higher order functions that call into your code. an interface is really just a record of closures. DI is the same as js modules, nothing new or exciting, it's just a workaround in pure OOP languages because they don't allow code outside of classes. fluent is just functors
So I watched about 5 of your videos, and I felt myself saying to things like "don't use comments" that this guy is taking everything to the extreme. Then about a week later, I found myself really cleaning up my code using different techniques that you've discussed, especially about inverting the logic of the if statements! I understand now that having knowledge of these different styles doesn't mean you have to use it 1000% of the time, but it really helps to sprinkle it in here and there and made your code that much better! Thanks my guy, you definitely earned my sub.
That's the next level of FP... it starts with pure functions and pipelines, but then introduces other types (like the scary "monad") which are off-putting words for simple concepts to solve this problem. But IMO they're worth it, because they really simplify error handling, and make it impossible to "forget" to handle errors. They bring errors into the normal flow of code, so you treat them with the care they require, rather than just slapping on some error handling after-the-fact
Well It's still fun, Monads has a type which is called Either it's basically so useful to return errors, you either take return left or right, .map, filter, etc.. works on right and mapLeft, peekleft etc.. work on the left part :)
what exceptions? errors are handled through a sum type, in Rust (which is not purely functional, but has arithmetic types with (imo) better names for some situations), this is done through Result, in haskell the standard is Either Left Right, if there's only one error case (like "the string you passed in is not a valid integer" for a parseInt) you can use Option / Maybe Type
@@LetsGetIntoItMedia does this mean that all functional pipelines must complete regardless of errors and depending on where the error occurred in the pipeline then you get a different error message / stacktrace at the end and then use an if statement to handle both success case and error case to do side effects? is that all this comes down to-the basic alternative to try catch / .then.catch?
The initial example of replacing a for loop with a function reminds me very much of how I would split loops up in JavaScript if I had to do a setTimeout between each iteration. I wasn't aware that solution was "functional programming!"
14:42 I tend to avoid chaining like that these days to improve debugability and testability. So many times where chained method calls have gone wrong, and you have to split them anyways.
I find that one of the biggest things that scares new people off functional programming is making functional languages less readable in order to make them more concise. For example, use of symbols over words. This is also true of e.g. add using + but the general population is familiar with +. If you read C it's relatively readable by comparison, and you could probably show someone on the street a while loop with an if inside it, and they could tell you what it does
i watched this video months ago and thought the non mutable idea was pretty cool'. but i took it on board. now i never write anything thats mutable and my code is so much better for it. i still use normal for loops tho. improved my coding dramatically. thank you so much code aesthetic!
Normal for loops are a good idea to avoid risking a stack overflow without other functional programming magic. Sometimes one can accept a little bit of state.
HOLY CRAP this helped me so much. I have written recursive functions and used them and even explained them in coding interviews but the way you broke it down here helped me understand even more! You lost me a little after the halfway mark but that just means I need more practice and more exposure to. Hella dope, thanks again!
Thank you for these videos. I see that it has been some time since New ones were added, but I have just discovered these, and they are very helpful - clear, well produced, good illustrations. Again, thank you!
Even in academic mindset, isn’t replacing a for loop with recursion just means that we are replacing explicit state with implicit state (meaning call stack memory)?
This chanel is just pure pure gold ❤️ I have exactly the same opinion about functional bros but I almost came to a point I felt bad mutating a state even though it made the code do the right thing in a clear and readable way. This video spackled joy in my heart :)
Dude... your videos are just so enjoyable. Been coding professionally for over 20 years by now and still I love to watch your videos. I sometimes even learn a thing or two. Keep them coming, you are really awesome and make coding more accessible to everyone.
I really enjoyed working with these functions in Angular. Mostly with rxjs though, which I find incredibly intuitive. Though I still used state everywhere, but I think just for the things that actually made sense.
Mutating state in JS is how you make things faster. It just is. The more array methods you do, the worse it gets for garbage collection because every new map() and filter() call creates a new object and copies all of the key pointers to the new array. Also recursive "data pipelines" as described are just a neat way to speedrun stack overflows and increase memory usage. In short, most of the "clean" code these days is just a direct downgrade from regular for loops.
Usually you don't actually need to have most of the code be faster. Like in this case the difference in time taken to send a web request to the users' bank for their transaction history will be thousands of times slower than the processing loop if you're running it synchronously. In a program like this, you're unlikely to run into performance problems unless you have many many users, and if you do, you should probably switch over from JS into a systems level language anyway, that is, if you can't just pay another £100 a month (a lot less than your lawyers) to increase the server size and stop the performance problems anyway.
If your expensive operation (A) takes 100ms and you reduce it to 50ms by switching to C++ or Rust, it won't matter if A gets called 1000x instead of 1x. The language won't help you from hitting some kind of limit if your code is bad. Taking functional programming to the extreme requires rerunning a lot of code because you aren't storing any of your past work. And if you are it's stuck in a closure which is more tedious to access from multiple places in your code. Functional programming has some good ideas, like avoiding side effects as much as possible, or *not* turning everything into a an abstract factory builder class. But like anything, you need to understand why you're doing the things you're doing without being ideologically attached to a narrow paradigm. Sometimes classes are a natural fit for some piece of data, sometimes you just want ephemeral functions that return and leave nothing behind. @@tupoiu
Ok but monads, functors, et al are pretty basic in practice but with scary names, and help you write super reusable code. Suddenly you write one function that can operate on all kinds of data structures, do I/O, conditionally run, etc. These kinds of array functions are the most used functors, but once you realize you can do the same thing for Optionals, Trees, event streams, etc is where it really becomes powerful!
Scary names and scary definitions (from Wikipedia): Functors are structure-preserving maps between categories. They can be thought of as morphisms in the category of all (small) categories. A contravariant functor F: C → D is like a covariant functor, except that it "turns morphisms around" ("reverses all the arrows"). More specifically, every morphism f : x → y in C must be assigned to a morphism F(f) : F(y) → F(x) in D. In other words, a contravariant functor acts as a covariant functor from the opposite category C^op to D. Pretty basic!
@@ximono yes because they are being very precise. But if you understand map (aka functors) then you can understand what monads are. As the only real difference is instead of returning T you return Monad.
I write C# in a functional style, mostly, but monads, functors etc. Aren't part of my daily vocabulary. If you want me to tell you what part of my code is what, I'm not able to do it 😂
One thing that would be great to mention at the end, is that in JavaScript, all of these built-in methods evaluate eagerly. Which is fine for small, synchronous sets of data, but what happens if you'd like to read the data directly from the backend - i.e. our we don't have an complete array, but our data items are fetched asynchronously? In the example, we only want the top 10 results for our dashboard, so we could stop iterating the transactions once we've satisfied that limit. With current code, we always iterate & filter through all the results, and then discard everything that's beyond the top 10, which is not very efficient. It works with simple examples, but when you start looking at situations with thousands complex data entries, this adds up and will have performance implications - both on execution time, as well as memory usage (our runtime creates many intermediate objects/values, and the garbage collector has to work overtime). This is a situation in which libraries like RxJS and IxJS can help, as they enable you to use functional style pipelines lazily, also on asynchronous data sources. Observables are coming to JavaScript natively, so hopefully this will be easier to achieve without additional libraries.
@@shinobuoshino5066 there always is, nothing is ever completely pure. Even in synchronous loops, each iteration of the loop will change the state of various registers within the CPU - index registers are updated, comparison flags are set, and the program counter is adjusted to jump back to the start of the loop. Intermediate objects might be created and garbage collected. There's no escaping the constraints of the real world (and hardware). Whether we retrieve the data from local memory, or from a remote server only differs by the underlying complexity of the iteration's execution, and by the addition of waiting on another thing to be able to start executing the loop code again.
"There shall be no state" Enter: State Monad 😂 Btw: many functional languages have an imperative-looking way of using, applying, binding functions, which makes this not as unreadable as you make it look here. In fact, functional code is generally much easier to read than imperative or OO code, as without side effect, what you see is what you get. You can reason about code, which is much, much harder with mutable state, where you never know what can happen, as any part of your program can change the state.
Well said! I'm admittedly a big FP nerd. Sounds like you might be interested in my series! For many applications, the mental shift has been really great. I've been able to "reason about code", as you say, in ways I really couldn't before
Functional programming terms cheetsheet: Higher order functions - functions can be stored in variables and passed around Currying - filling in some functions with some arguments and calling it with the rest later (more commonly called binding) Monads - shorthard to quickly do A if a value is a special case, or B otherwise, in a chain. In javascript, "?." is a monad operation which accesses the property if it can, otherwise it just gives back undefined. In rust, enums are nearly always monads. Functor - a function which calls (or explains how to call) a function on the inner value of the monad, depending on what it is.
@@BlackPenguins17 That's crazy. How would you know - does he show it somewhere? I cannot for the life of me see that that would be productive, vs using an animation program. I wanna learn how :)
Please make more videos!! Ive never had the nuances of programming seen explained this well and I think this is really valuable! Well done and thanks!!
One problem with functional programming, at least in Javascript, is that the performance overhead is quite large. Constantly making new arrays and objects significantly slows down the code. Mutating objects is just so much faster, which is unfortunate, because good functional programming is so fun to write and read!
Which is why going for a language that has functional programming as a goal from the beginning is a huge advantage. Most of the overhead is mitigated by the compiler making the right decision in those cases. Like, in #fsharp, there are cases where the CLR restructures your code to be procedural imperative style, and gives you the perf benefits of that. I think Ocaml does it too.
I primarily use javascript, but this is one of the reasons I really want to use Rust. You get to do functional iterators that don't have that overhead, and it allows for mutability too.
You’re explanation with visual of currying made it make sense to make. I literally sat there dumbfounded with how powerful that is. 8:39 REALLY, really great explanation and great video. Thank you.
Great video building the concept from the ground up Ironically those of us that grew up with JQuery were encouraged to learn and use this from the beginning with method chaining and now it seems like users of more modern JS frameworks are rediscovering this with ES6 lol
@@charlietian4023 No. That is a basically a strawman. In FP one would use a higher level function that does the loop. Like `map`. One would not write it recursively. That is idiotic. Recursion is applied when a problem is naturally recursive. Like processing a tree-shaped data structure.
Pure FP with immutability is not fake - the point of immutability is that you just allocate more and more memory when you make copies, rather than change what’s already there. It means concurrency is baked into the language. This might feel inefficient but there are data structures to mitigate the performance and memory overheads. It’s common to feel this sentiment if you approach FP through the lens of OOP. It’s true that FP isn’t ready to market itself well, and this video suffers from the same problem as all the others.
most FP data structures are 'algebraic' or more generally 'functorial' so you don't have to copy them just their references (and this is safe due to immutability) but it does come at the cost of no data locality
@@AndreiGeorgescu-j9pafaik data structures in FP are quite often trees or some other complex stuff which supports cheap copying by reusing parts of copied structure. It's all stored by reference and probably scattered in the heap. Data-locality is usually achieved by tightly packed values allocated in array.
@@AndreiGeorgescu-j9p the latter, they are automatically functorial but there are in general more structures than just those (each algebraic data type can be interpreted as a functor in the bicategory of types (endofunctor in the category of types))
@@AndreiGeorgescu-j9p data locality refers to fitting it in the cache so, a bunch of references bouncing around the memory is unsuitable for that von neumann purpose
One thing that always bugs me when working with F Bros is that they even eschew mutation of data structures that the function wholly owns. This can lead to really poorly performing code that creates and destroys a ton of data in memory when simply mutating would be vastly more optimal. I get that premature optimization is a thing, but this is a bigger footgun the more you go down the functional rabbit hole, as all the indirection it can introduce will mask these kinds of performance problems in impenetrable stack traces of anonymous functions. Talented F Bros can deal with this but your typical coworker will just be happy to get the result they want working with this gilded Rube Goldberg machine created by the F Bro, and will likely have no idea they’re introducing major performance problems.
As a person who is extremely repelled by functional programming at first glance, this video really helped me take a better look at it. Might try this programming paradigm with some of my projects.
Definitely don't do it in JavaScript like he did. Its not a functional language and doesn't fully handle the paradim. Instead use a language like Rust, everything he did here can be condensed to nearly a single line.
@@amaryllis0 the point of FP is to show what your doing over all. Quickly at a glance you can see your filtering mapping and sorting an array of data. Spreading out across multiple lines gives no advantage and ends up just being messy and more likely to loose performance gains. For instance i have a project where a user can put in a phrase and it transcribes it to a fictional language All i do is User_Input.chars().filter(|c| c.is_ascii_alphabetic() || c.is_whitespace()).collect::().to_uppercase().split_whitespace() With this you can quickly and easily see I'm going through the characters removing anything not a space or english letter and then turning it into a string then uppercasing it and splitting it by into individual words. Its quick, clean, and concise in its purpose. And i can do all of that in a single statement.
Functional programming as an all encompassing paradigm just isn't useful but that is what is frequently marketed. The whole point of most applications is to produce side effects and a programming style that excludes them is obviously not going to get the job done. But as a preferential style it is incredibly useful. Rather than writing code with no side effects, having a goal of removing all unnecessary side effects can have a huge effect on increasing the stability and readability of your code. When you isolate and tuck away all of the stateless code in your program, the actual necessary and relevant state just falls out into a few places where a previously incomprehensible code base becomes much clearer and easy to maintain.
Situation is currently this one: Programmers control a database via the non-functional language SQL which is translated by the database system into an operator tree, i.e. a functional program, for optimization. The database is programmed in an imperative language which the compiler translates to a functional internal register language for optimization. The result of the optimizer is written to imperative assembly language code. The CPU analyzes data dependencies and reorders instructions accordingly, that is, it would also benefit from a functional machine language. Summarized: Functional programming already reached world dominance but at all occurrences it is pretended that imperative programming would be the normal way of programming, would be machine oriented and efficient.
"There Shall Be No State"
Anarchists 🤝Functional programmers
❤🖤
I vibe with this, but no matter how much functional propaganda I consume, I do not understand how it's possible to write a full program without state. Let alone something like a game.
@@halotroop2288you pass the state around as an argument. So in a game you'd be passing the game state through your game loop and update it accordingly. :)
@@okk2094 that sounds like imperative programming with extra steps
@@adora_was_taken it's a different way to solve problems. Of course you could mimic it in other paradigms.
It's similar to Data Oriented Programming, which is fairly popular in game dev too.
Code Aesthetic: Makes a whole video about being a "never nester"
Also Code Aesthetic: 4:10 puts an entire function's logic inside an if statement instead of using a return.
As well as multiple early returns within the if 😂
Early returns are not there in most functional languages. So it's not common to use them. The same is true for local variables. Creating them usually requires a `let ... in` construct so they are also avoided
The fact that he chained function calls after function calls... it has the same effect to the call stack as recursion does... smh right now
Judging from their second channel and their website, I think they've been on a journey to rediscover non-OOP and computer science since the never nester thing. Also, it's not a bad thing to change your mind.
My guess is it was just for ease of understanding.
He's showing how the condition moves down to the recursive solution so any change there is not necessary for the point.
Would've been nice to see him do the solution in the video and simplify it to flatten the if statement.
I learned functional programming via F# and Clojure, and came away with a rather impure lesson: You only need a SUBSET of pure functional concepts to write great software. I've found that in most languages, you get 80% of the benefit from 20% of the tools: Referential transparency via expression-based programming; no state or local-only state (avoid global state or state passing at all costs); chained stateless transformations, via things like universal function call syntax, pipe operators, map, filter, reduce, and comprehensions; a strong preference for passing around and transforming simple data structures, such as records/structs; liberal use of tagged unions/variants to create exhaustive conditionals that make illegal states unrepresentable; and making functions short and sweet, which allows for minimal nesting and easy naming. Your program then becomes a pyramid of calls to what is essentially a DSL.
Hard agree. I’ve been all the way down the FP rabbit hole and back again. My takeaway is that the biggest bang for your buck is functional style state management. Usually there is a framework that does this for you. Use it.
Agreed, using imperative code inside the local scope of a function is a good trade off to maintain simplicity and performance, so long as your exported function maintains referential integrity
F#❤
I somewhat agree. But then, what are examples of stuff you consider to be functional but out of that subset you mention? To me, you summed up what functional means, and I don't get why you would call that an "impure lesson" (other than for the local-only state). 🤔
"Your program then becomes a pyramid of calls to what is essentially a DSL."
*Lisp curse intensifies*
Seriously look up the lisp curse by Winestock. The problem is that in a team of 5 people, they'll be able to come up with at least 6 different DSLs for the feature set. And which one is the most correct is usually dependent on future unknown expansion of feature set.
Lambda calculus (which most of the FP is based on) is not just about purity and statelessness of functions and converting loops to tail recursion calls. It ensures transparency of the functions. The entire tree of terms is always available. Which means, the compiler can reduce expression trees, sees data access patterns, can replace operations and data structures with more efficient ones, eliminate effectively dead code, etc., and do so across the function calls. These techniques are used even in imperative language translators (e.g. GCC and Clang), but they are tied by unpredictably mutable state.
But full immutability also makes a lot of things harder. I implemented a compiler in Haskell once. It was not fun, at all. Want to modify a syntax tree while type checking it? Have fun rebuilding the entire tree, which is both error-prone (accidentally changing left-hand side and right-hand side of binary operators or using older (not type checked) nodes) and makes code harder to understand. Or you can use the same syntax tree and make the same checks every time (like dealing with == operator for arrays differently). Or maybe there is some fancy way of doing it with a shit ton of abstraction that I couldn't figure out because I don't have a PhD in category theory and computer science. Ironically, Haskell taught me the beauty of procedural programming.
@@AndreiGeorgescu-j9p the comparison between mathematics and programming is weird, because they are mostly unrelated in this context. Even if you want to go along with it, mathematicians use a mix of mathematical notation and kind-of-imperative natural language, 'cause explaining everything using only maths and logic would be unreadable.
Maybe functional languages are good for toy compilers (that probably use parser and lexer generators), but any serious compiler like gcc or nodejs uses procedural/imperative style, with hand written parsers. Most of the widely used languages combine procedural or imperative with other paradigms.
It's weird to say that procedural style is garbage when Haskell literally reinvents it. What is a state monad? It's Haskell reinventing mutability. What is IO monad? Just statements in imperative languages. Why do you need those if functions are so good? Cause you can't program in a convenient way using only functions without adding a shit ton of abstractions and concepts.
To be fair, these optimizations also happen in imperative languages, especially if the compiler does effect analysis. Of course, guarantees are more reliable than compiler inference. This is not to say functional languages always yield inherently more performant programs, that should be left up to benchmarks in practical applications.
@@AndreiGeorgescu-j9p you completely missed the point about mathematics. Are math articles/books written in math notation only? No, because it's unreadable. Do teachers throw pure formulas at students? No, they explain it with words + show some formulas/definitions. At no point did I claim that mathematical language is imperative. "How to prove X? Show, this, this and that", that's pretty imperative. Whatever, this discussion is pointless, you seem to be one of those pure functional programming zealots.
@@AndreiGeorgescu-j9p "It definitely doesn't make things harder, that's why mathematicians use "immutability", aka real variables. No mathematician is writing "imperative" math."
this is factually incorrect (and a misunderstanding of mathematics). If I define the variable x = 1 in one context, and x = 3 in another context, my x is not universally immutable; it is contextually immutable and it may be a different value in a different state. Not to mention that there is an entire category of mathematical functions that are iterative over some mutating variable en.wikipedia.org/wiki/Series_(mathematics)
The functional emphasis on reducing state and minimising side effects also, in my opinion, greatly helps with reducing code complexity in the way described in "Philosophy of Software Development". In that book, he emphasises creating modules or functions with deep functionality, and simple interfaces. If your functions are pure and have no side effects, it simplifies the actual interface and avoids information leakage - you only have to know what the function explicitly does, what its parameters are, and what it returns. The implementation is entirely irrelevant. I've definitely edited some people's code where I've been confused by bugs, only to find that a function somewhere is sneakily mutating the state of the code when I'm not aware of it. Having a guarantee that a function doesn't do such a thing is really useful when trying to quickly understand and use a system. I think something like naming functions which are pure as this_is_a_function_P and ones which have side effects as this_is_another_function_Imp would potentially help, provided people do stick to those rules. Just my two cents!
With JavaScript, you can customize ESLint to give you errors or warnings when a function modifies its inputs. You can customize the rule to not report errors if your parameters are written with a specific pattern. I configured it so that if you intend for an input to be modified (like when using an array.forEach()), your parameter name should start with "mut_". We have eliminated non-pure functions thanks to this
that should be something that the compiler enforces, for example in swift when you want to change some value of the containing struct inside of a method, that method has to be explicitly marked with the mutating keyword, otherwise you will get an error
so you want a const guranatee? we can have that in oop too :)
@@voidmind that fixes part of the problem, though it still doesn't stop you having some class method foo(int a) {b = a; self.c = 20; return b}, so doesn't guarantee pure functions as side effects are still possible. However, that sounds like a good approach to do this in a language which doesn't enforce these kinds of rules
@@LosFarmosCTL yep, or rust for example has all data as const by default, which is a much better way than the standard mutable by default approach, though I doubt many other languages will adopt it sadly
Important to note that in JavaScript, Array.sort() sorts the array in place. However, ECMAScript 2023 introduced a new non-destructive method for sorting an array, Array.toSorted().
PS. It also introduced Array.toReversed() and Array.toSpliced(), non-destructive alternatives to Array.reverse() and Array.splice().
now if only they'd give us summed, grouped, unique, and partitioned.....
yes, you can do literally all of that in `reduce` but reduce is notoriously easy to write and hard to read. also, they have you put the initial value _after_ the function, which is just sacrilege.
@@Bliss467 the initial value after the function is correct because in bind-order the function is what you would bind first when specialising it under partial operation
Can we petition to introduce Array.toMapped, Array.toFiltered, etc. that mutates the original array to just really destroy the souls of new developers?
@@cadekachelmeier7251 I think those functions are both ridiculous though.
Array.prototype.toMapped = function(transform){this.forEach((e,i,a,)=>{a[i]=transform(e,i,a);});};
// this just uses for each!
toFiltered would either be inefficient OR take up extra memory.
inefficient way:
Array.prototype.toFiltered = function(condition){
// yes, this for loop is horrendous
for(let i = 0, c; i < this.length; c?(i++) :(this.splice(i,1)))
c=condition(this[i]));
return this;
};
extra memory way:
Array.prototype.toFiltered = function(condition){
const f = this.filter(condition);
// apply is used because ...ellipsis is slow
f.unshift(0);
f.unshift(this.length]);
this.splice.apply(args);
return this;
};
@@Bliss467 Object.groupBy now exists!
This pipeline approach is one of the things I love about Rust. In JS, the array methods operate on the entire array at each step, whereas Rust iterators work on a single item from the iterator at a time across the entire pipeline.
That's one thing I'm always a little annoyed about in JS, the functional methods are *only* defined on arrays, not iterators. Which sucks sometimes. Apparently they are in development on iterators but its not implemented everywhere, including node.js, so like... not really usable
@@electra_ Yeah the tc39/proposal-iterator-helpers seems to be missing the implementation step. I'm really excited to get something like that in js.
C# LINQ also operates directly on the IEnumerable. So it does everything item by item instead of always processing the whole collection.
@@MegaJoka100 yep, god I love enumerables because of that lol
Also, JS copies arrays in ram to work on them (which sucks even more if you chain multiple of those functions together), while Rust "resolves" your chain of functions at compile time. It's part of Rusts zero-cost abstraction philosophy. Rust is awesome.
Recursive functions can be scary because it can lead to very long call stacks and stack overflows. I recently learned about tail calls which some languages can use to avoid this. Essentially, if the last line run in a function is another function, the rest of the resources from the completed function can be freed up. I learned about this from Lua, but it looks like there is some compiler support in C++ as well
Same thing exists in Scala 😉
Scheme's spec mandates that implementations guarantee tail recursion. WASM has a tail recursion extension as well. JavaScript was supposed to get guaranteed tail calls, but it was veto'd by Apple and Google IIRC. A lot of compilers (for example Rust's) will do TCO or tail call optimization, but this isn't guaranteed, so I personally find it annoying to rely on. I don't want to write higher level code and then check the assembly to see if it's actually optimized the way I want it.
Of course it's basically guaranteed to be available in functional languages like Haskell, OCaml, Elixir, etc.
Even though it works, it really feels like a hack. What if the compiler accidently does not recognize this for some reason? Then you have unexpected behavior that might also be hard find out about.
@@ZhilBear in case of Scala if you want to make sure you can use a simple annotation. If you write something wrong compiler will tell you right away
@@joranmulderij once you get used to it, it's simple enough to reason about. for example, if i want to write a non-tail recursive factorial, it will look like this:
func factorial(int n) {
if n
As someone who is trying to learn programming, and has only so far found OO educational materials, I have had a lot of trouble trying to comprehend what a functional approach would look like. I finally get it. Thank you so much! I feel like I have the power to ask better questions now, and keep moving forward.
it looks like a lot of code that noone can read. Because at the end of the day, at the level of the CPU and even before function and data is the same thing.
In my opinion it is ok to concatenate functions in a left to right syntax, but having the ARGUMENT and not the result of a function be another fucntion (so kind of right to left) is just making your code unnecessarily hard to read
in fact this is why SQL can be frustrating sometimes, although CTEs help
@@lucaxtshotting2378 Ah yes because OOP is notorious for being easy to understand, and having no unintuitive egregious anti-patterns.
As an early career developer without a CS or mathematics degree, I really appreciate how you explain things and your delivery. I would love for you to do a series of deep dives into various principles and paradigms. Whatever you do I'm sure it'll be great and I'll be here for it.
Happy holidays, stay safe 🎄
This was a really good execution of a 3b1b-style "rediscovering the fundamentals from scratch" journey. Also, I have a sudden urge to take pictures of my receipts and upload them somewhere, but I'm sure it's completely unrelated
Google Opinion Rewards gives you $0.10 to $0.20 of google play credits every receipt. #sponsored
Heyy, you're that rivals of aether mod creator. I see you 👀
and he declared war on CODERIZED channel also⚔
woag
Wait I just did that! Could it be…? No…
The timing on this video is perfect. I've been on an fp video binge the last few weeks and you have a way of visually explaining things that I've always appreciated.
Have you tried #fsharp?
This video was a really great overview! Since you're on an fp video binge, maybe you'd be interested in the series I'm starting (check out the channel page). I think there's also a lot of value to understanding some of the math concepts... NOT because smart words make you smart, but because the smart words are actually fairly simple concepts that are quite universal, so knowing them helps you see patterns in problems, which you can then solve more simply
@@LetsGetIntoItMediaThanks for sharing. I just watched the first video in the series and I think I'll enjoy the series and learn a lot from it.
@@kylewollman2239 lovely, thanks for watching! I hope you enjoy the rest. And please leave me any feedback on the format/pace/content, so I can tweak the series to be most fun and helpful as possible!
Thank you very much! I've only ever learned OOP, and have been struggling to properly conceptualise functional programming for months, but this video made so much sense. Ive been seeing those snippets of functional programming code at work, it is good to be able to better understand the method and thinking behind them.
@@AndreiGeorgescu-j9p have you considered uploading your own video?
Take note that doing FP in JS may induce a steep performance penalty, for you are allocating a new array at every chained step and copying the data. And in most cases the runtime cannot optimise this because the closures have no pureness guarantee (i.e. may have side effects).
If you want the true functional experience, learn SML or Haskell. If you want the practical functional experience and still keep your sanity (i.e. the choice to opt-in/out at will), learn Rust.
I like rust less as its not actually functional, instead the trait system is quite good.
I'm not sure the performance penalty is at all something to worry about in web dev with JS. What data collection are you working with on a web app directly, that is large enough for performance to take a noticeable hit? Even if we make some ridiculous large shopping cart or whatever, with 1000 of different items added, it would still take a FP approach less than the blink of an eye to process - on an old and bad PC!
I think you're prematurely optimizing for something that would never be an actual measurable difference. In turn, you're losing the FP way which can be easier to reason about and way more concise.
(oh yeah, I said it won't even be noticeable in an old and bad pc with an unreasonably artificial huge shopping cart. If it's a standard budget modernish laptop then it even make less sense)
Imagine if all developers thought like that, software would be even slower. I'm a huge FP fan, but I won't use it for languages that don't have compiler support to make it fast (e.g in-place opportunistic mutation). "Premature optimization".... lol, people really need to stop using that term.@@marouanelhaddad8632
@@AndreiGeorgescu-j9p By what definition of sane?
You can very easily define an array mathematically, same thing with a circular buffer... @@AndreiGeorgescu-j9p
Fantastic video! I love FP and dislike the F-Bro lingo as well. My gross oversimplification of FP to regular programmers has always come down to:
1. Write more pure functions.
2. Use more immutable variables.
Nearly all the complexities of FP can be derived from making these two happen, yet MANY good, simple patterns in any language also stem from these.
Hey what happened to the channel, I am not seeing amy new videos anymore..😢
Monads/applicative functors are going to be the next FP feature to appear in mainstream languages I think. The usefulness of piping a transformation that can fail/have multiple outcomes is just so great compared to infinite 'if' checks, scary runtime exceptions, or unpacking multiple lists only to map over them immediately after. Kotlin already has a "access member if not null" operator, Rust is similar with its Result and Option datatypes.
We already have flatMap in js arrays so almost there
I implemented Result/Option in C# using a Nick Chapsas video as reference, and they've been a godsend for cutting down on null exceptions and all the guard clauses to prevent them. If only they were built-in, I'm sure they'd be way better than what I cobbled together haha. Definitely not as nice as Rust, where they're core language featues, but way better than nullable or relying on null checks.
I really hope so! I think the Effect library will do a lot to bring those to TS in a very usable way. I was using fp-ts before, but found it clunky. Effect might make it all usable enough to actually get popular 🤞
@@TheMasonX23actually, neither Result nor Option are core language features; they're perfectly standard data types that you could implement equivalently in user code (the difference is that because they're part of the core _library_ they're ubiquitous in APIs, which wouldn't be the case for a user code version)
@Starwort Well, the `?` operator isn't something you could build yourself, and it makes using Result and Option *so much nicer*.
sorry, you got the wrong person, me and my lifestyle are DISfunctional.
Dysfunctional*
Dusfunctional*
Dizfunkshunul*
Rizzfunctional*
Deez funkshions are belong to us
If you're going to make such a good video on Functional programming, you gotta mention Iterators and Lazy evaluation. I'm not a functional bro, but I know they love those things. They're the reason why in javascript arr.filter().map().take()/reduce() is O(3n) and in rust its O(n). No, we're not dropping the constant here, if you get carried away with "functional style" in js, your app will really show its lack of iterators.
For those unaware, iterators will basically turn our data pipeline on its head where instead of looping the array 3 times for each transformation, we're going to process 1 item at a time and apply all transformations sequentially on it. And this IMO is key for marketing someone to functional programming, because otherwise functional just looks like slower less optimized code.
What's funny is that rust has iterators, but functional bros would get angry if you called rust a functional language. Some even get mad if you call Ocaml functional, since ocaml has for/while loops.
btw, Ramdajs is a library that is well known for two things. 1. Functional style, lodash-like helper functions and 2. terrible performance, due to lack of iterators. So bad that it spawned another library that sold itself as "ramda but smaller and with better performance" and one of the core things they did to speed it up was add iterators (among other things).
As a scientific programmer (in chemistry) who uses Julia intensively after bad experiences with C: I feel like to first order, state is the enemy of scientific programmers (more generally, when state is not relevant to a problem, it should be abstracted away as much as possible). Functional programming maps very naturally onto a lot of what we do, but a lot of the trouble people around me had was the degree to which we were taught we had to manage state when we really didn't.
I love the visualisation of the functions!
Btw, you would be super cool if you did a video about array programming! Like with APL or BQN etc. Those are the most aesthetic language in my opinion and it would blow a lot of peoples mind if they saw that video!
I mean he kind of set it up. We now need a video on functional combinators, monads and then we can slowly start to swallow the array pill and have our 20 lines of BQN code that form an Excel clone, a web server, the entirety of Facebook, a missile guidance system, a hospital management software and a smart thermostat all in one go.
you say that like array programming is the next step, but i have my doubts. it seems to lack the composability of functional programming by imposing more arbitrary limitations. from what i remember, there is also a lack of higher order functions in array programming
@@xybersurfer it’s funny that you say it lacks composability when that is one of the strongest features. Code_report (TH-cam channel) has some good videos showing it 😁 also comparing with Haskell. They also have operators (in APL speak) or modifiers (in BQN speak) which are sort of higher order functions, but BQN (the language I write in (I have some videos on my account if you’re interested)) also supports higher order functions in the functional sense. And the restriction (constraining to arrays) isn’t arbitrary, it gives a lot of power and expressivity exactly due to limiting the baseline to a bedrock foundation and building from there using the most flexible and essential functions and making them compostable in every way you’d want. I personally really love them, and if you get your mind into array thinking they’re super expressive, powerful and fun to work with
@@davidzwitser oh then perhaps these limitations are specific to the variant of array programming that i used. i looked at the video "I ❤ BQN and Haskell" by code_report. it's shorter than Haskell, but it looks like write-only code (similar to APL). to me it would make more sense if this form of polymorphism was added to Haskell
he is a gem in content creation
I recently watched a video on "hardware programming" which I now realize is just basically building functional programs into hardware.
von neumann is rolling in his grave because of you comment
This is a nice video! I'm extremely a fan of declarative paradigms, but it's quite hard to get others to understand why it's cool. Functional is just one step there. Stuff like logic programming is even more intriguing, though haven't have much opportunity to get into it yet. At least SQL is known by everyone and IMO one of the best arguments to be made for people dissing on declarative - imagine writing this complex SQL query manually! I think that should help people understand the value of declarative too.
I love declarative stuff as well. I do have to say that it is very often not the best way to solve a problem, but in cases where it does work, it is often by far the best solution.
@@joranmulderij Dunno about the domain you work it, but for me it's almost seems the best, at least as bulk of code. As video author said yeah you need to wrap it around on edge IO cases, but bulk imo very often can be done that way. At least in web dev and shell pipelines. Also a lot of times there it seems to not be the case IMO existing surrounding code is the reason why and not that it would not work. Just some imperative stuff is so proliferate that it's very hard to do work with it. Imo in future it will be declarative almost always everywhere, at its way more composable, and also giving way more leeway for compilers/systems to decide on how to execute (think SQL changing execution plans automatically based on what it knows about indexes/data stats and what not). For me it seems like a tragedy that imperative was allowed to poison software development for so long. But then, as you can see I have strong opinions :D
@@morphles The main downside to declarative programming is that it is way easier to get yourself stuck. You really need to "design" the interface. But once that is done and your use case fits tightly without what the declarative system was designed for, then the DX is amazing. I'm currently writing a declarative nodal reactive state management library with two way data flow, which would allow you to have a full-stack reactive system without getting your hands dirty with caching mechanisms etc.
I learned to love FP after I gave up to understand *what* a monad is and started just using what the language offered me. Rust's Result type is such a cool concept! *Months later:* **Wait, that's a Monad**!!? Anyways, that literally doesn't change anything, back to using the cool Result type!
Ignore the occult name we give those fellas, just look at *how* they work and you'll find it absolutely fun and game changing!
Don't search *what are monads for christ sake*.
Search *how to use Rust Result type? When and why is it useful?*
This will forever change your learning curve to a satisfactory realization that yeah, I guess those Monads are cool, I use them all the time! What is a monad? No idea! I can show you an example on how some are cool though, wanna see?
The currying explanation finally made it click for me, good video mate! Also even if a FP lang is not truly "pure" having immutability and the semantics that brings, it gets a lot easier to reason about code.
It's not curry, it's partial application that is "side effect" of currying
That's cool and stuff, but this example only works because you only use functions that operate on lists and use fluent pattern. The good thing of functional programming is that you can pipe anything as long as output of previous matches input of the next. Absense of nulls and exceptions and adts are also nice. Monads and all that wizardry is just a consequence of using it all together
And it also works because every function being piped doesn't require any additional information beside what is in the list. As soon as you need to propagate some information, you end with a lot of useless arguments that are just being passed around. Like what if you want to know the index of filtered element in original array? You now need to create an array of pairs (index, element), and every function needs to accept that pair and propagate that index through every call. That is error-prone (what if you accidentally pass an incorrect number somewhere?) and clutters the functions (that index is not used for anything in most functions).
@@clawsie5543 you're doing it wrong. You don't need to flatten everything down to pass it along, just have that part of the chain as a closure over the original index and the code that needs to reference it, just does :shrug:
Never change, functional programmers; never change.
immutable, const
My job had a massive ball of state that was a slow, difficult to navigate mess.
Over two grueling months, I replaced a massive section of the base with pure functions. Nothing crazy or convoluted. Just functions that transformed the data the app was required to transform.
The result was seamless parallelism with Java's parallelStream( ) API, 100% test coverage (for that section), and total eradication of classes like "AbstractParserFactoryBuilder".
I was a functional fanboy. When I finally walked the walk, I became a whole ass functional disciple.
TBH Functional Programming is very much a means of carrying over principles from analog data processing (such as control theory and analog computing) into the world of digital computing.
Filter design, block diagrams & transfer functions, feedback & feedforward, hysteresis, etc. are all from the world that existed before digital took off.
Good point. Explains why Gary Sussman prefers languages with a lisp.
There are actually multiple hardware description languages, used to describe circuits to imprint on a silicon die or an ASIC, that are based on functional programming or simply are functional programming languages. More traditional HDLs like Verilog and VHDL are almost _weirdly_ imperative for something intended to model essentially a pipeline of gates and circuits wired directly into other gates and circuits.
Code Aesthetic: please keep posting such videos. Don't give up on code education
This channels has 8 videos and 300k+ subs. Shows the quality of the videos. Keep them coming please. DI was my favorite so far.
As a JS developer, the best extract I got from functional programming is being mindful of how I funnel state changes in my code. Pure functions help a lot, as well as understanding the difference between a shallow and deep copy. And yes, I'll favour map, forEach, reduce, along with most array methods, over imperatively written loops.
Be aware that in javascript, many of these operations will have significantly higher CPU and memory overheads vs more traditional approaches, particularly with how much JS loves to mutate and copy arrays under the hood. This is not to say you shouldn't use them, composing reliable and easy-to-read functions is an extremely powerful way to write easy to parse and easy to debug code, but it should be applied carefully and checked for performance when used in performance-critical areas of code. As someone who writes a lot of performance-critical JS (irony, I know), I generally avoid them for that reason, but they have their place.
@@thehobojoe javascript is already slow, might as well take advantage of that to write more readable code
One of the biggest benefits of keeping things pure is that it's easy to write unit tests.
I am an "FP bro" and must say that your code was a little bit complex. I usually split the pipeline steps into variable assignments to make it more self documenting, unless its steps are very simple.
Totally! Testing is not a way to catch bugs, it's also a great way to force better code design. If something is easy to test, it's probably written pretty well. And FP is great for that since everything is simple input-output. Mocking, particularly, becomes a non-issue
@@LetsGetIntoItMedia Agree 100%. Just like Rust gives a guarantee of memory safety, pure functions give a sort of guarantee that the function does one thing only. Regardless of the logic that produces the transformation. One can even take it to the next level by writing property based tests. I find writing property test very difficult, but for the more capable and skilled programmers out there it can give extreme code coverage.
Ohhh yeah, I find property based testing really difficult in practice too. Because then it adds a bunch of complexity in generating the random inputs, and making sure that you cover all the possible edge cases. That, to me, is not worth it. I'd rather just keep functions small, and have tests be very very hardcoded. Think through your edge cases and manually write them all out. And if a production issue comes up, add that as a new unit test to catch that regression in the future.
One of my teammates once said, "tests are only worth it if they save you more time/pain in bugfixes, production issues, middle-of-the-night emergencies, and reputation than the amount of work required to write them". I think property testing is over that threshold.
@@LetsGetIntoItMedia Good point. What do you feel about using a debugger? Personally, I'm so bad at programming that I actually spend 80% of the time stepping through the failing tests in the debugger until I find the root of the issue. I used to hate the debugger, but now I'm starting to become comfortable with it.
There is no other way 😁 when I was first introduced to a debugger, it blew my mind. (But I'm about to contradict myself. There are definitely other ways, and often necessary...)
But that was in Java, where stack traces are much nicer. In Javascript, there are so many anonymous functions and library code that it makes it much trickier. Particularly in pipelines, since there's not an obvious place to even place a breakpoint. I end up using pipe(fn, fn, a => { debugger; return a; }, fn) a lot. The debugger is still a good tool, but Javascript makes it tricky. Next best thing is to keep trying to isolate pieces of code and run them in smaller test cases. Unit testing is key.
Honestly for me the sweet spot has always been a mix of functional and OOP. In another word, I would write immutable functions that don't have any side-effects. However if it comes to function state or currying. I would never write a function that returns another function, cauz at that point you're basically creating the same thing as a class, that has an immutable state (read-only properties). And between a class and currying, I would choose a class over a binding function for readability, visibility and testability concerns. As a matter of fact, even a class should always have pure and functional methods imo. And this is what I meant with mixing.
That's why there's no clean code where you use only one style. And you can't glamorize only OOP or only functional. I found mixing to be the best.
Functional bro here. That's probably my favourite "very beginner functional stuff video" now.
I had ~10y of Dart+JS+TS before turning into (Pure) Functional Bro writing stuff in Haskell. Most of the videos about functional programming, at some point, make me think "this guy doesn't know shite and talks nonsense". However this is video is nearly perfect in this regard.
However the video only covers only tip of the iceberg. Lambdas and map/filter/reduce are pretty much everywhere. Some languages are even getting algebraic datatypes (just calling them differently). Some languages actually have stuff that are almost monads tho hardcoded for specific cases (Promises and nullable types, yeah). These are cool and all but it's all a bit of shoehorning - functional paradigm works poorly in essentially imperative languages the deeper you go with functional stuff. Syntax is a bit awkward because there is no proper function composition or currying. You have to be careful with performance because in absence of purity and lazy computations you'll can end up reallocation same array bajillion times. You can bork the stack because you don't have TCO or it's not guaranteed to kick in. In the end it's almost only syntax sugar with awkward syntax at the end.
Unfortunately most mainstream languages won't ever get the coolest parts of the iceberg like referential transparancy, advanced typesystems and stuff that depends on that. Just because you have to design the language for it from the beginning.
What I'm trying to say, I guess, is: don't judge functional programming and functional bros (except the snobby smug ones - judge them to hell) right away for "impractical math nonsense" from your essentially imperative language point of view. F bros are using totally different language.
Good point about the almost monads. Iterators, streams, option/result types, etc. - eventually people will notice the common pattern and realise they've been using monads all this time :)
The song name in the outro music is called: Novembers - Just Ripe (Album is called Pour Me a Cup of That)
I don't like the idea of paywalling the song names fam
Thanks. I'm trying to find the first song he used. No idea why it's behind a paywall on his patreon when it's not even his music.
@@DigiiFox Just shazam it brother
These videos are always so insanely good. Incredible channel!
@@AndreiGeorgescu-j9p Get off your high-horse, and stop spamming the same copy & pasted reply. Have fun with your pseudo-intellectual (and possibly imaginary?) job as well. Jesus...
Prof. who teaches Java here, and I have fallen in love with streams. Kotlin in particular has some absolutely gorgeous lambda based stuff in their stdlib.
Functional bro here! Guilty as charged. You're totally right...
The concepts aren't actually that complex (a Monad is basically something that has a map() and flatMap(), and you can arrive at the need for that with a few simple code examples), *but* the words can be used to gatekeep or show off your smartness
Also, the focus is often elegance for elegance's sake, rather than usefulness, simplicity, composability, safety, testability, etc. Though it is, in a way, the elegant foundations in math that allow FP patterns to get so powerful
Also, I do think there's value in the words. It would be extremely difficult for society to function if we never put labels on things... how could we ever succinctly talk about anything without terms for concepts? Learning the FP words are useful not because knowing definitions is useful, but because they help you see those patterns in a problem space and then go r you immediate strategies for breaking them down into smaller bits and solving them concisely
@@AndreiGeorgescu-j9p Lol what a big non-response from you. Not only were you unnecessarily condescending, you never even addressed what he didn't understand about monads. You never addressed the elegance for elegance's sake argument, and then spent the rest of it inflating your own self-ego about being using functional programming. Then you all wonder why people don't take functional programming seriously.
@LetsGetIntoItMedia I think the responder is a perfect example of why functional programming has a hard time making traction in comparison to oop when it comes to marketing it's benefits. A lof of of the folks trying to push it, spend way more time focusing on impractical simple examples that aren't of much help to anyone building applications, and seem to just use it as a way to flex how smart or clever they are.
Hmm yeah, I had a lot of thoughts to that response (which I'm not seeing anymore here, but saw the notification). My take is that I actually agree with a lot of the points that person made, but just absolutely NOT the tone or extremity.
I totally agree that engineers *should* be into math, as they are literally writing computer math. But people come from all sorts of backgrounds, and that doesn't necessarily include a love for math. It might just be a love for creating stuff from more of a design perspective, or just a plain need for a job. And that's good, because we get a variety of perspectives and experiences in the field. People often *don't* think of math as something enjoyable, or realize how much code is like math. That's not a problem, that's an *opportunity* to share a new wonderful perspective.
I also agree that FP isn't as off-putting or "gatekeepy" as the stereotypes suggest, but that's because I'm a math nerd on the side. But fact of the matter is, it *is* a stereotype, which means many people think it is unapproachable, and therefore by definition it *is* unapproachable. If most people agree that a balloon is red, then it probably *is* red.
There is jargon in OO as well, that's for sure. And jargon isn't a bad thing. It's a way to capture a whole concept in a single word. So if everyone agrees on its definition, it makes communication much quicker and more efficient. *But* that's why, when talking to people who don't know those words, you have to do a good job explaining them. The problem is, based on the general sentiment, that people *don't* do a good job explaining the jargon, which feels like gatekeeping.
Also, re: my definition of a monad, of course it's simplified. There are monad laws and all that, but that's exactly the stuff that turns people off. The average person doesn't need to know the exact precise definition of a monad, just some practical examples of how they can help simplify problems.
Whew... I wasn't sure if I wanted to respond to that message, but I decided to make my perspective known. There's an *opportunity* to make the community better by *proving those stereotypes wrong*
dude this is the most clean explanation of functional programming ever created.
This is absolutely hands down the best explanation I’ve ever seen on functional programming. I’m only a few minutes in and I’m just so blown away I had to comment. Definitely the best visualization of what’s going on, this is literally so extremely helpful for understanding this concept which I’ve been struggling to start to understand. Thank you so much, you make beautiful and helpful videos!
The point of functional programming is not to "have no state" it's just about how to effectively manage that state, this is the whole point of a monad!
The problem with functional programming in a language like JavaScript is that the JS runtime does not optimize functional techniques. Languages like F#, LISP, Scala, etc. can eliminate stack overflows and sometimes even do something called loop fusion where, in your pipeline example, all operations happen in a single loop instead of looping multiple times which is always the case in JS.
you shouldn't be recursing anyways. usually for every recursive implementation there is a less error prone higher order function
@@marusdod3685 example please. Here's a prompt: you need to keep calling a db or api until the nextToken stops coming back
@@SogMosee What you are looking for is called an "unfold" or more generally an "anamorphism". It's type looks like:
const unfold = (fn: (state: B) => [A, B] | null, initialState: B): [A] => { ... }
and you would use it like:
const results = unfold(nextToken => {
if (!nextToken) return null;
const { data, nextToken } = callApi(nextToken);
return [ data, nextToken ];
}, "");
The implementation is left to the reader, turns out "unfold" is a pretty useful higher order combinator that pops up everywhere. Obviously it's not a javascript builtin and obviously we don't do promises here etc but yeah if given a toolbox of higher order functions, we usually don't need to recurse.
And thinking about it, `unfold` is pretty similar to generators found in most programming languages, perhaps even equivalent.
Your animation of a statefull function was a work of art!!!!
did you mean stateless instead of stateful?
@@xybersurfer no
That segue to a "sponsor" was brilliantly done 🤣
I love monads because they are impossible to understand theoretically, but take a half second to understand what it's doing when you actually see one even if you have no clue how monads are supposed to work.
If you're already writing a bunch of procedural code, and using all your language features, you should already be intuitively doing all this (If you've been coding long enough anyway). I've noticed that LLMs almost never give you functional code unless you're super specific in asking for it, and even then they will often mess up, and go back to managing state. This is because most people aren't optimizing, and so most the training data isn't optimized but I digress. The video was beautiful.
what an interesting observation, dare I say ive noticed that shit too!
Absolutely stunning and I was quite amazed by the clarity of this video. As a total beginner to functional programming (with years of experience in OOP and recursion), I find it crispy clear with the visuals that you created. Thanks again for this work.
in the context of software development, mutable states are allowed if they are local to the functions. And vois la, just like that, loops are back in business, and you don't need to rely on recursion you might blow up your callstack if you are looping of a large dataset
Tail-call optimisation is all you need, bro.
not a functional bro myself, but i fell most of you are missing the most brilliant thing about FP: code-as-data. In a way, this is what every monad ever written is about, have your code broken in small steps that declare what you want to do and not how you want do it.
Taking react as an example, component functions aren't just plain JS functions, they're monads! Components can do one of those things at a time: use a hook, run synchronous code, and return a JSX tree. those 3 kind of smaller steps are chained together in a plain js function to declare what your component do. ReactDOM.render() and react testing library's render() take that same data (your component) and interpret it in different ways.
Migrating from stack to fiber was just a react dom update, because the only thing that had to change was the render method, data stayed the same.
With react the same monadic interface (mixing hooks, sync code and JSX) can be used to render into the dom, render native apps, program embedded systems, create music, video and a lot of stuff. that's way monads are nice.
And you don't need to go full-haskell-mode to benefit from that :D
and if you wanna learn more about react in that context i would recommend Lin Clark's "A Cartoon Intro to Fiber" talk
The issue with stateless programming is that in real world objects _exist_ and they _have_ state. And most of domain specialists and product owners which you eventually communicate with, they use human language, and this language uses objects and states. This immediately leads to your common mental model of domain logic would have stateful objects. And then you would try to map stateful model on stateless paradigm. And then 99% of you start writing unreadable code.
Assuming code objects should correspond to real world objects is a common mistake in OOP.
"You only need a SUBSET of pure functional concepts to write great software. " ,
One issue is that many of the more well-known functional languages are a pain to get into. Scheme is easy to pick up at first, but it quickly becomes challenging to tackle problems that are a breeze in other languages. Haskell is wonderful, if you can ever get it to compile. Some of the newer functional languages, though, like Clojure and Elixir, make FP much easier to get into.
It is probably true that the most useful languages take some functional features (e.g. Rust), but it's good to have some experience with functional programming so you know when it's useful to bust out the maps, filters, and reduces.
lmao I always assumed functional programming was terrifying so hadn't looked into it further but it's just recursion and lambdas??? That's crazy. I'll have to look into it more. Thanks for the video!
I'm gonna get in trouble for this but functional programming has always seemed to me like when this guy showed be how he draws in Excel and I'm like, hey, that's cool. Only the functional programming people want to do it in production.
Exactly, like f bros have to touch grass at some point, because this is a philosophy that’s not 100% applicable to real life
I've absolutely loved LINQ in C# for years, but only after learning Rust and more about FP in general, and I realize now that it was the use of higher order functions and FP paradigms that I enjoy so much. Also, I was blown away be the realization that Tasks are monads that I've been using this whole time...
The man the myth the legend!
Bad marketing for different paradigms is rampant across the programming discipline. At this point, I actively ignore its propenents until I've had a chance to learn more about it. Ironically, they suck the fun out of it for fresh newcomers.
Functional brogramming
One huge benefit I've seen in my own code by adopting functional principles is that it reduces your time complexity. Even in the example given, the functional pipeline breaks a O(n^2) down to an O(n). It's really easy to slap another nested for loop into something, but when thinking in terms of transforming pipelines there isn't nested code just one function after another.
I work on an internal data viz platform for my work, and I've found in the frontend trying to rearrange data from the backend to be easier to display is a lot easier when using this style of programming. RxJS that comes with Angular enforces that while making your app "async" (I pipe user inputs through RxJS and have tried to make it more functional from there). Also using MongoDB on the backend with it's appropriately named "pipelines" feature is all functional transformation to make views and reports of data. It's amazing to see a full stack app where tons of functional programming can be used.
Certain things have many excited proponents that can't explain "why it's good" to anyone but each other. (Like, in my opinion: IOC, DI frameworks, fluent syntax.) I find most of these aren't worth using in pure form most of the time. However, my takeaway from functional programming class was that I felt extremely certain how my program behaves. After I got over the big mind flip! It's data processing didn't stick in my mind.
fluents are kinda crazy
Is IOC just callbacks?
You are basically using IOC when working with frameworks. Let's say you have a game engine. Basically you just put your code in custom class and implement a couple of callback methods eg. Init() and Tick() and these methods will be called by the framework itself. It's good because you shouldn't care about who is calling these methods, what is the right order and preconditions, framework just doing it for you.
Why is it good? Because you can abstract yourself from some things and focus on your features, which is basically why people use frameworks.
DI containers are essentialy a mechanism for constructing your objects and providing dependencies. You can sure do that manually in composition root, where you build all your objects in specific order so all constructors can receive valid dependencies, and objects that created internally have their dependencies too, so it gets quite tedious and not very productive.
With DI framework you can just define that "Foo is provided for IFoo as singleton" or "Bar is provided for IBar as new instance each time".
Then if you want to add new class, you just write its code as usual, list all dependencies in constructor, then you bind it in container and it will resolve automatically providing all dependencies for you. Want to add new dependency? Just add it to constructor and it will just work.
Specifically in games if you want to create new object from factory, factory don't need to know all the dependencies required for an object, DI container can provide all the dependencies for you. Isn't that good?
All of those patterns are FP in disguise.
Inverson of control is just making use of higher order functions that call into your code. an interface is really just a record of closures.
DI is the same as js modules, nothing new or exciting, it's just a workaround in pure OOP languages because they don't allow code outside of classes.
fluent is just functors
@@marusdod3685 by that logic your FP is literally procedural in disguise, because in the end it's all just data in memory and function pointers.
So I watched about 5 of your videos, and I felt myself saying to things like "don't use comments" that this guy is taking everything to the extreme. Then about a week later, I found myself really cleaning up my code using different techniques that you've discussed, especially about inverting the logic of the if statements! I understand now that having knowledge of these different styles doesn't mean you have to use it 1000% of the time, but it really helps to sprinkle it in here and there and made your code that much better! Thanks my guy, you definitely earned my sub.
It's all fun and games until you have to deal with exceptions in your functional pipeline.😅 Btw really good video
That's the next level of FP... it starts with pure functions and pipelines, but then introduces other types (like the scary "monad") which are off-putting words for simple concepts to solve this problem. But IMO they're worth it, because they really simplify error handling, and make it impossible to "forget" to handle errors. They bring errors into the normal flow of code, so you treat them with the care they require, rather than just slapping on some error handling after-the-fact
Well It's still fun, Monads has a type which is called Either it's basically so useful to return errors, you either take return left or right, .map, filter, etc.. works on right and mapLeft, peekleft etc.. work on the left part :)
what exceptions?
errors are handled through a sum type, in Rust (which is not purely functional, but has arithmetic types with (imo) better names for some situations), this is done through Result, in haskell the standard is Either Left Right, if there's only one error case (like "the string you passed in is not a valid integer" for a parseInt) you can use Option / Maybe Type
@@catgirlQueer you explained it, so that was what I was saying tho.
@@LetsGetIntoItMedia does this mean that all functional pipelines must complete regardless of errors and depending on where the error occurred in the pipeline then you get a different error message / stacktrace at the end and then use an if statement to handle both success case and error case to do side effects? is that all this comes down to-the basic alternative to try catch / .then.catch?
The initial example of replacing a for loop with a function reminds me very much of how I would split loops up in JavaScript if I had to do a setTimeout between each iteration. I wasn't aware that solution was "functional programming!"
1:58 functional programming = anarchism, got it
Last 1m 30s of the video was phenomenal. Loved the music and overarching summary.
14:42 I tend to avoid chaining like that these days to improve debugability and testability. So many times where chained method calls have gone wrong, and you have to split them anyways.
You have no idea how excited I am when I see a new video of yours. As always, great video!
I find that one of the biggest things that scares new people off functional programming is making functional languages less readable in order to make them more concise. For example, use of symbols over words. This is also true of e.g. add using + but the general population is familiar with +. If you read C it's relatively readable by comparison, and you could probably show someone on the street a while loop with an if inside it, and they could tell you what it does
i watched this video months ago and thought the non mutable idea was pretty cool'. but i took it on board. now i never write anything thats mutable and my code is so much better for it. i still use normal for loops tho. improved my coding dramatically. thank you so much code aesthetic!
Normal for loops are a good idea to avoid risking a stack overflow without other functional programming magic. Sometimes one can accept a little bit of state.
HOLY CRAP this helped me so much. I have written recursive functions and used them and even explained them in coding interviews but the way you broke it down here helped me understand even more! You lost me a little after the halfway mark but that just means I need more practice and more exposure to. Hella dope, thanks again!
Thank you for these videos. I see that it has been some time since New ones were added, but I have just discovered these, and they are very helpful - clear, well produced, good illustrations. Again, thank you!
Even in academic mindset, isn’t replacing a for loop with recursion just means that we are replacing explicit state with implicit state (meaning call stack memory)?
This chanel is just pure pure gold ❤️
I have exactly the same opinion about functional bros but I almost came to a point I felt bad mutating a state even though it made the code do the right thing in a clear and readable way. This video spackled joy in my heart :)
Dude... your videos are just so enjoyable. Been coding professionally for over 20 years by now and still I love to watch your videos. I sometimes even learn a thing or two. Keep them coming, you are really awesome and make coding more accessible to everyone.
I really enjoyed working with these functions in Angular. Mostly with rxjs though, which I find incredibly intuitive.
Though I still used state everywhere, but I think just for the things that actually made sense.
Mutating state in JS is how you make things faster. It just is. The more array methods you do, the worse it gets for garbage collection because every new map() and filter() call creates a new object and copies all of the key pointers to the new array. Also recursive "data pipelines" as described are just a neat way to speedrun stack overflows and increase memory usage.
In short, most of the "clean" code these days is just a direct downgrade from regular for loops.
You get decent performance in functional languages, javascript is only "pretending" if you will. It is very clearly an imperative language
Usually you don't actually need to have most of the code be faster. Like in this case the difference in time taken to send a web request to the users' bank for their transaction history will be thousands of times slower than the processing loop if you're running it synchronously. In a program like this, you're unlikely to run into performance problems unless you have many many users, and if you do, you should probably switch over from JS into a systems level language anyway, that is, if you can't just pay another £100 a month (a lot less than your lawyers) to increase the server size and stop the performance problems anyway.
If your expensive operation (A) takes 100ms and you reduce it to 50ms by switching to C++ or Rust, it won't matter if A gets called 1000x instead of 1x. The language won't help you from hitting some kind of limit if your code is bad. Taking functional programming to the extreme requires rerunning a lot of code because you aren't storing any of your past work. And if you are it's stuck in a closure which is more tedious to access from multiple places in your code.
Functional programming has some good ideas, like avoiding side effects as much as possible, or *not* turning everything into a an abstract factory builder class.
But like anything, you need to understand why you're doing the things you're doing without being ideologically attached to a narrow paradigm.
Sometimes classes are a natural fit for some piece of data, sometimes you just want ephemeral functions that return and leave nothing behind. @@tupoiu
This is really mind blowing! Probably the best video I’ve seen on TH-cam this year! Thank you very much for making this! ❤❤❤
Ok but monads, functors, et al are pretty basic in practice but with scary names, and help you write super reusable code. Suddenly you write one function that can operate on all kinds of data structures, do I/O, conditionally run, etc.
These kinds of array functions are the most used functors, but once you realize you can do the same thing for Optionals, Trees, event streams, etc is where it really becomes powerful!
Scary names and scary definitions (from Wikipedia):
Functors are structure-preserving maps between categories. They can be thought of as morphisms in the category of all (small) categories. A contravariant functor F: C → D is like a covariant functor, except that it "turns morphisms around" ("reverses all the arrows"). More specifically, every morphism f : x → y in C must be assigned to a morphism F(f) : F(y) → F(x) in D. In other words, a contravariant functor acts as a covariant functor from the opposite category C^op to D.
Pretty basic!
@@ximono yes because they are being very precise. But if you understand map (aka functors) then you can understand what monads are. As the only real difference is instead of returning T you return Monad.
@@ximono yes, they are basic, it's literally a level 2 concept in category theory (ie, the second thing you ever learn, the first being morphisms)
Gosh I love your videos, I hope you make more
I write C# in a functional style, mostly, but monads, functors etc. Aren't part of my daily vocabulary. If you want me to tell you what part of my code is what, I'm not able to do it 😂
This is the best lead up to how map works I've seen, good work
5:56 uh don't break your tail recursion please
One thing that would be great to mention at the end, is that in JavaScript, all of these built-in methods evaluate eagerly. Which is fine for small, synchronous sets of data, but what happens if you'd like to read the data directly from the backend - i.e. our we don't have an complete array, but our data items are fetched asynchronously? In the example, we only want the top 10 results for our dashboard, so we could stop iterating the transactions once we've satisfied that limit. With current code, we always iterate & filter through all the results, and then discard everything that's beyond the top 10, which is not very efficient. It works with simple examples, but when you start looking at situations with thousands complex data entries, this adds up and will have performance implications - both on execution time, as well as memory usage (our runtime creates many intermediate objects/values, and the garbage collector has to work overtime).
This is a situation in which libraries like RxJS and IxJS can help, as they enable you to use functional style pipelines lazily, also on asynchronous data sources. Observables are coming to JavaScript natively, so hopefully this will be easier to achieve without additional libraries.
>fetched asynchronously
are you implying there's state and side-effecty things going on in the backend?
@@shinobuoshino5066 there always is, nothing is ever completely pure. Even in synchronous loops, each iteration of the loop will change the state of various registers within the CPU - index registers are updated, comparison flags are set, and the program counter is adjusted to jump back to the start of the loop. Intermediate objects might be created and garbage collected. There's no escaping the constraints of the real world (and hardware).
Whether we retrieve the data from local memory, or from a remote server only differs by the underlying complexity of the iteration's execution, and by the addition of waiting on another thing to be able to start executing the loop code again.
"There shall be no state"
Enter: State Monad 😂
Btw: many functional languages have an imperative-looking way of using, applying, binding functions, which makes this not as unreadable as you make it look here. In fact, functional code is generally much easier to read than imperative or OO code, as without side effect, what you see is what you get. You can reason about code, which is much, much harder with mutable state, where you never know what can happen, as any part of your program can change the state.
Well said! I'm admittedly a big FP nerd. Sounds like you might be interested in my series! For many applications, the mental shift has been really great. I've been able to "reason about code", as you say, in ways I really couldn't before
Gotta love how someone tagged the super obvious joke as a sponsorship spot in sponsorblock
Excellent video, I would also have talked about fold/reduce.
Functional programming terms cheetsheet:
Higher order functions - functions can be stored in variables and passed around
Currying - filling in some functions with some arguments and calling it with the rest later (more commonly called binding)
Monads - shorthard to quickly do A if a value is a special case, or B otherwise, in a chain. In javascript, "?." is a monad operation which accesses the property if it can, otherwise it just gives back undefined. In rust, enums are nearly always monads.
Functor - a function which calls (or explains how to call) a function on the inner value of the monad, depending on what it is.
These animations are absolutely incredible! What software are you using to create them?
He uses a python script.
@@BlackPenguins17 That's crazy. How would you know - does he show it somewhere? I cannot for the life of me see that that would be productive, vs using an animation program. I wanna learn how :)
@@carlerikkopseng7172 I think I saw it on his patreon. I paid for a month to see the library.
Please make more videos!! Ive never had the nuances of programming seen explained this well and I think this is really valuable! Well done and thanks!!
One problem with functional programming, at least in Javascript, is that the performance overhead is quite large. Constantly making new arrays and objects significantly slows down the code. Mutating objects is just so much faster, which is unfortunate, because good functional programming is so fun to write and read!
Which is why going for a language that has functional programming as a goal from the beginning is a huge advantage. Most of the overhead is mitigated by the compiler making the right decision in those cases. Like, in #fsharp, there are cases where the CLR restructures your code to be procedural imperative style, and gives you the perf benefits of that. I think Ocaml does it too.
I primarily use javascript, but this is one of the reasons I really want to use Rust. You get to do functional iterators that don't have that overhead, and it allows for mutability too.
Not large enough to matter
You’re explanation with visual of currying made it make sense to make. I literally sat there dumbfounded with how powerful that is. 8:39
REALLY, really great explanation and great video. Thank you.
Great video building the concept from the ground up
Ironically those of us that grew up with JQuery were encouraged to learn and use this from the beginning with method chaining and now it seems like users of more modern JS frameworks are rediscovering this with ES6 lol
"functional programming bros are bad marketers" ... proceeds to rewrite a for loop as a recursive function
Isn't that the point?
@@charlietian4023 No. That is a basically a strawman. In FP one would use a higher level function that does the loop. Like `map`. One would not write it recursively. That is idiotic. Recursion is applied when a problem is naturally recursive. Like processing a tree-shaped data structure.
@@ElizaberthUndEugen I see, can you provide an example? So CA isn't the most familiar with FP either
@@charlietian4023 he goes into `map` etc. in the last third of the video.
@@ElizaberthUndEugen I'm aware how map works, but could you provide an example of how that's used instead of iteration or recursion?
Oh man that Black Sky ad!!! Amazing. A comedy interlude was the last thing i expected 2/3rds of the way though this
Pure FP with immutability is not fake - the point of immutability is that you just allocate more and more memory when you make copies, rather than change what’s already there. It means concurrency is baked into the language. This might feel inefficient but there are data structures to mitigate the performance and memory overheads.
It’s common to feel this sentiment if you approach FP through the lens of OOP. It’s true that FP isn’t ready to market itself well, and this video suffers from the same problem as all the others.
@@AndreiGeorgescu-j9p what is bro yapping about. please shush
most FP data structures are 'algebraic' or more generally 'functorial' so you don't have to copy them just their references (and this is safe due to immutability)
but it does come at the cost of no data locality
@@AndreiGeorgescu-j9pafaik data structures in FP are quite often trees or some other complex stuff which supports cheap copying by reusing parts of copied structure. It's all stored by reference and probably scattered in the heap.
Data-locality is usually achieved by tightly packed values allocated in array.
@@AndreiGeorgescu-j9p the latter, they are automatically functorial but there are in general more structures than just those (each algebraic data type can be interpreted as a functor in the bicategory of types (endofunctor in the category of types))
@@AndreiGeorgescu-j9p data locality refers to fitting it in the cache so, a bunch of references bouncing around the memory is unsuitable for that von neumann purpose
One thing that always bugs me when working with F Bros is that they even eschew mutation of data structures that the function wholly owns. This can lead to really poorly performing code that creates and destroys a ton of data in memory when simply mutating would be vastly more optimal. I get that premature optimization is a thing, but this is a bigger footgun the more you go down the functional rabbit hole, as all the indirection it can introduce will mask these kinds of performance problems in impenetrable stack traces of anonymous functions. Talented F Bros can deal with this but your typical coworker will just be happy to get the result they want working with this gilded Rube Goldberg machine created by the F Bro, and will likely have no idea they’re introducing major performance problems.
As a person who is extremely repelled by functional programming at first glance, this video really helped me take a better look at it. Might try this programming paradigm with some of my projects.
Definitely don't do it in JavaScript like he did. Its not a functional language and doesn't fully handle the paradim. Instead use a language like Rust, everything he did here can be condensed to nearly a single line.
@@thegoldenatlas753 Why would you want to condense a bunch of distinct operations onto a single line? That's terrible code design
@@amaryllis0 the point of FP is to show what your doing over all.
Quickly at a glance you can see your filtering mapping and sorting an array of data.
Spreading out across multiple lines gives no advantage and ends up just being messy and more likely to loose performance gains.
For instance i have a project where a user can put in a phrase and it transcribes it to a fictional language
All i do is
User_Input.chars().filter(|c| c.is_ascii_alphabetic() || c.is_whitespace()).collect::().to_uppercase().split_whitespace()
With this you can quickly and easily see I'm going through the characters removing anything not a space or english letter and then turning it into a string then uppercasing it and splitting it by into individual words.
Its quick, clean, and concise in its purpose. And i can do all of that in a single statement.
@@amaryllis0 Because nobody uses rust in production, mainly bc rust devs are awful
Functional programming as an all encompassing paradigm just isn't useful but that is what is frequently marketed. The whole point of most applications is to produce side effects and a programming style that excludes them is obviously not going to get the job done. But as a preferential style it is incredibly useful. Rather than writing code with no side effects, having a goal of removing all unnecessary side effects can have a huge effect on increasing the stability and readability of your code. When you isolate and tuck away all of the stateless code in your program, the actual necessary and relevant state just falls out into a few places where a previously incomprehensible code base becomes much clearer and easy to maintain.
Glad you're still posting! 😊
Situation is currently this one: Programmers control a database via the non-functional language SQL which is translated by the database system into an operator tree, i.e. a functional program, for optimization. The database is programmed in an imperative language which the compiler translates to a functional internal register language for optimization. The result of the optimizer is written to imperative assembly language code. The CPU analyzes data dependencies and reorders instructions accordingly, that is, it would also benefit from a functional machine language. Summarized: Functional programming already reached world dominance but at all occurrences it is pretended that imperative programming would be the normal way of programming, would be machine oriented and efficient.