Introduction
Before we get started with coding I want to give you a quick introduction to object-oriented and functional programming.
Bo...
For further actions, you may consider blocking this person and/or reporting abuse
When you say 'Functional' do you mean to say procedural? BASIC is a procedural language, but definitely not a Functional language, whereas languages like F#, Haskell (like you mention), Scala are Functional languages.
The reason I ask is because you give more of a view of Procedural vs OOP, rather than functional vs imperative (procedural/OOP with state maintenance and change) as an example.
The OOP example you provide could still be viewed as functional in the sense you don't mutate state.
If functional - in the sense you are operating on immutable functions only - is truly the goal, perhaps an example where the OOP approach maintains and mutates state?
Beyond that though, it's well written article.
You can do OOP with immutable objects. In js numbers, strings are objects but immutable.
But Date on other hand is mutable. Having some objects mutable some immutable leads to bugs if one does not know what are doing.
But regardless if it's mutable or not, state is mutated somewhere still.
Take redux reducer for example
it's immutable function on the surface, but it returns new (updated) state - of whole application state, thus there is a power to change anything in application state here. Mutability is not the main problem. Using purely immutable interfaces can help avoid certain type of bugs. But eventually it leads to the same big problem - somewhere state is updated, and the big question is, how is it encapsulated.
A careful developer would divide state in small chunks and ensure all reducers only take care of one little piece of state
not careful developer still can just as easily add unpredictable side effects even though interface is immutable
In redux on can also do same thing that one can do with setters/getters in object
now one can modify state anywhere from app code - immutable and functional version of setters/getters antipattern of OOP
Immutability and functional development it self doesn't do that much. It's good but it's not a lot. One must also follow SOLID and other better known guiding principles (like Law of Demeter) to build good code and it doesn't matter OOP or functional.
You are totally right, I struggled to get a good example that is small and simple enough for beginners to understand. I think bringing in state and managing it would have been a bit to much for beginners.
I hope the point I wanted to make gets more clear now.
Thanks for the constructive feedback I really appreciate it :)
I think that trying to turn functions into classes may not have produced the most convincing examples. :)
I think that the biggest problem is that oop requires a more explicit structuring of domains of responsibility -- it might be easier to go from an oop design to a functional design.
That's a good point. My intention was not to go too deep into the details and try to find a simple example.
Maybe the order was a bit confusing.
Thanks for the feedback :)
You're welcome.
This is more like OOP vs Procedural comparison indeed :)
By the way you can use recursion for your factorial function
Nice article :)
Thanks for the feedback. I linked to geeksforgeeks for more details on the implementation possibilities for a factorial. I decided to go for the iterative approach because I didnāt want to introduce recursion in this article š
Yep, just seen it :) I was thinking to opposite, factorial is good chance to show off the recursion :)
To me week spot of functional code in js is composition.
I think this quickly becomes hard to reason about it once you get little larger codebase, because it can be hard to find code that is gonna run, when function is passed as argument and all you see when opening some deep module is this
When instance is passed as argument - it has a type (it's class name) - and so it's very easy to locate related code even without typescript.
with typescript it's possible to achive similar end goal with just functions but in expense of declaring a lot of additional types (while with classes - class it self already is a type). With all additional type declarations functional approach can get even more verbose that class based approach.
Lambda calculus deliberately only deals with single argument functions because they compose well.
For functions with multiple arguments you have to curry to preserve composability.
TypeScript pushes JavaScript deeper into 'class-based object-orientation' territory (apart from soundness being a non-goal) - so any deviation from that ideal will require a deep dive into the typing metalanguage.
Statically typed FP is better served by ReScript.
Quote
IMHO currying in javascript only makes things worse
I don't need to curry in order to make anything more composable. I could always compose simply by declaring new function
number of things is better in this approach over curried version:
So in nutshell currying to me is a solution for non existing problem which makes things worse when applied religiously.
In my initial example I did used one case of a 2nd order function, which was a case of curried function. But this was done in order to separate dependencies from data. I would do that only if I know usage patterns
This implies that intended use of it is of is to be instantiated before usage.
and definitely not
createFunction()()- if there is a case to use it like this then it's better to declare it with flat arguments.To me problems with this functional approach and composed functions happen when composed function must be passed down as parameter. This is where to me class based approach wins over function based approach. After function is passed as argument, it becomes more difficult to trace which code is relevant when function is executed. It's not necessary a problem when writing a code but it's a problem when you are in large code base and want to understand it. With typescript can declare interface of function but that does not help finding implementation. On other hand in class based approach receiver can indicate concrete class that it expects (even without typescript - with js docs).
Your initial composing function
accepted arbitrary single argument functions.
Now your preference
is to manually assemble the function.
I was referring to generalized function composition. Given that functions only return a single value general function composition can only compose functions that accept a single value. In that context currying is the workaround to fake multi argument functions.
I wasn't advocating "curry all the things" just for the sake of it.
This style
is related to
i.e. using a closure to mimic partial application - it's just that "some code here" is never isolated into an independent function.
Your particular annotation identifies
dependenciesas arguments to a kind of constructor: "A closure is an object that supports exactly one method: apply."Passing a function as an argument to a higher order function is equivalent to passing a strategy object to a context object (Strategy Pattern) - i.e. this kind of composition exists in both paradigms.
What you are saying is that you find it more difficult to work with interfaces than concrete implementations (i.e. this isn't about functions vs. objects). That may be so but:
"Design Patterns: Elements of Reusable Object-Oriented Software" p.18
i.e. classes depending on other concrete classes should be the exception, not the rule.
The natural boundary around a class that depends on other "concrete classes" automatically includes those "concrete dependencies" (and recursively their concrete dependencies). This creates a much larger unit that needs to be "reasoned about" as a whole.
Interfaces are at the core of many OO practices including the dependency inversion principle.
You probably have other, bigger problems in the code base when you have difficulty tracking down the concrete implementation that is used to service a particular interface at a call site.
It's also a running joke that function types satisfy the interface segregation principle by default.
The impression I'm getting here is that you find imperative code ("do this then that") easier to read - which isn't surprising given that most of us learn programming that way - it's the allure of the familiar.
Functional programming tends to focus less on the "how" and more on the "why" and "what" (some say it's more declarative) - but it still has its 'step-by-step' moments. For example:
Now
transformis a terrible name, eventextToFactorialwould have been better but at the time I was trying to make a general point. But the "steps" are still there, clearly outlining what is going on. The big difference (to imperative code) is that this code isn't transforming any data at this point - the function that will be transforming the data is being "wired up".Functional code is composed of functions and as functions are generally smaller than objects there will be proportionally more code dedicated to "wiring up" the capability rather than "doing" the capability. So when reading the code one has to differentiate between "construction" code (the scaffolding) and "running" code.
But the same is true for any non-trivial object-oriented code base. As it grows and God Objects are avoided more and more code is dedicated to setting up the relationships between the collaborating objects before they can do any useful work.
However a network of interacting stateful objects can grow in complexity rapidly. A composition of stateless functions (or immutable closures) is typically easier to reason about. Coming from an imperative background the functional approach is different enough to take some getting used to.
(One issue with React hooks is that functional components are a now just as stateful as objects - which gives rise to much richer (i.e. complex) behaviour - the standing argument is that hooks are more declarative than object methods but that is a whole discussion its own).
It's not that I have difficulty. It's just not as fast. With OOP when coding on top of interfaces tooling supports jump-to-implementation. It's just less straight forward with function types.
I do not advocate towards breaking any best practices. That said not every practice that is necessary when designing reusable code is also necessary or even good when building one off pieces of implementation.
Strategy is just one use case. There are many cases. In UI applications hardly anything passed down element tree is 1st order function. If it was 1st order most of time you'd not pass it down - it can be imported and called directly.
Well firstly I think it's little bit condescending for you to make impression of why I "find imperative code easier to read".
But regardless of that, I think this is a terrible argument. Normally if someone does not find my code immediately straight forward I tend to believe that I made it too difficult. Sometimes I just couldn't think of easier way. Sometimes simply because I did something I thought was clever - turns out it wasn't.
Now to your example. If you're writing in this style then at least you could get rid of fn1, fn2, etc and that closure pattern - all that make it very ugly
to me very big downside of this code is that it's very hard to use debugger on it. Almost in no point can you place a breakpoint on it except for parseInt() part
And what is reason for it? I don't think there is good one. It's very imperative style in the end. Why would you write imperative code in declarative-functional-composition? Just makes no sense. There are situations where declarative style is great. There are situations where imperative style is great. Use one that is the most appropriate. Application like this would be best split into input, validation, action phases. Validator can be setup in declarative way. The rest can go in imperative. And result is best of both worlds, easy to follow, easy to debug.
Not sure where this is going.
As far as I'm aware "higher order function" doesn't imply an actual ordinality.
Eric Elliot used first order function to refer to a function that doesn't "take a function as an argument or return a function as output". Eric Normand on the other hand uses:
So I can only conclude that the "n-th order function" terminology with reference to higher order functions is neither standardized nor commonplace.
"Higher order function" simply calls attention to the fact that a function is specialized by and/or returns another function. But in the end in the functional style it is as natural to return functions and take them as arguments as it is to return an object and take object arguments in the object style.
The impression I'm getting is that you're saying that "a function that takes a function that takes a function" (and beyond) is getting hard to track. But given that functions have a type, a function can simply transform one type of function into another type of function.
No condescension was implied. The point was familiarity bias - Rich Hickey style i.e. easy is a result of familiarity, not simplicity and the unfamiliar can seem difficult even when it's simple.
At its core JavaScript is an imperative language - it just happens to also have first class functions and supports closures which can be leveraged when practicing a functional style.
And given that TypeScript keeps coming up, it's been my observation that TypeScript is much less conducive to enabling a functional style than JavaScript - to the point that it could be argued that TypeScript is the "wrong tool for the job" to support a statically typed functional style. I know of fp-ts but you have to work much harder in TypeScript to practice a functional style compared to an object style. But that's not the fault of the "functional style" but a result of TypeScript being streamlined for OO style typing.
That "ugliness" exists for illustration purposes - being explicit about the bound values being functions and the manner in which those functions are being composed.
That argument keeps coming up. You can still set a breakpoint at any function declaration and you can run into similar problems with dynamically assembled objects.
One could just as easily argue that it is disappointing that
I think the "hard to debug" argument has even more of a negative impact when it comes to adoption of streams which could potentially simplify UI architecture or perhaps enable some alternate approaches.
The code isn't imperative, the style is. Even Haskell has the do notation ("Haskell is the worldās finest imperative programming language").
Some people find it more intention-revealing.
The functions are organized in the run-time sequence of the composed function so that it's clear what the transform will do. The
{ok/err}type implements the necessary Railway-Oriented Programming (viaandThenandmap).To me that sample code seems influenced by Go:
"It must be familiar, roughly C-like. Programmers working at Google are early in their careers and are most familiar with procedural languages, particularly from the C family. The need to get programmers productive quickly in a new language means that the language cannot be too radical."
There's anecdotal evidence that something like How to Design Programs (HtDP 2e) may be a better first exposure to programming: āItās mind boggling that your HtDP students are better C++ problem solvers than people who went through the C++ course alreadyā.
The Structure and Interpretation of the Computer Science Curriculum
TL;DNR
While JavaScript is an imperative language (i.e. it isn't a functional programming language) I think it's fair to call it "Function-Oriented".
From that perspective mastering functions and closures is an essential part of developing JavaScript competence.
While "class-based object-orientation" goes back to Simula (1962) and Smalltalk (1972) it largely became mainstream due to languages like Java (1995) and C# (2000). While both Java and C# have gained a number of features that aren't directly related to classes over the years, it is probably accurate to say that their fundamental base unit of composition is a class instance, i.e. an object.
The same isn't true for JavaScript. The base unit of composition is a function and its stateful counterpart the closure. On a fundamental level object literals are simply associative arrays where the keys are limited to Strings and Symbols while the value can be of any type (in modern JavaScript Map fits the use case of an associative array much better). The notion of an "object with methods" emerges when functions are stored as values in a plain object (the prototype chain largely exists to help reuse functions across multiple objects).
While in Java and C# classes and class instances (objects) are atomic units of composition, in JavaScript an object in the object-oriented sense is an aggregate of a plain object and functions as values.
Personally the mental model of an object as an aggregate clarified the role of the function context
thisimmensely.thisis a special reference that gives a function access to some "other data". If a function is meant to act as a method (is accessed via an object reference) it needs to usethisto refer to the rest of the "object". Butthiscan also be used to pass any other context with call (or apply) and bind can be used to create another function with a "bound"this(an arrow function'sthisis automatically bound to the scope it's defined in). But a function is also free to ignorethisentirely.In my judgement the introduction of the module first as a pattern and then as a language feature is more important than the adoption of the
classsyntax sugar. It's possible to create a well structured code base with just modules, functions and plain objects.In fact during the ES5 years (2009-2015) an alternate model of object-orientation not based on constructor functions or classes but instead based on object factories emerged (OOP with Functions in JavaScript). A factory creates a plain object holding functions that are linked to "object state" not via
thisbut through the shared closure that created the functions. It's an approach well worth being familiar with in order to get better acquainted with closures.So in JavaScript object-orientation isn't class-based (
classis more of a creational convention) and the base unit of composition is the function (and plain objects). Objects compose as well and objects can compose with functions and closures. As a result objects (and more so classes) aren't always the "go-to" building block in JavaScript - functions and closures often play the role that small classes do in mainstream OO languages.Aside: There are some opinions that Closure components are a more obvious solution than hooks for stateful functional components (Hooks reimagined, Preact Composition API).
Functional programming is primarily about composing functions to transform a value.
So I might write the "functional-style" version as:
Edit: It's useful to remember Master Qc Na's lessons:
Using a closure
versus using an object
compositionandpipecould be replaced with:or
IIFE (Immediately Invoked Function Expression)
Great article, thanks! It would be great if you could write more similar articles and an insight on when to choose a paradigm over another.
Thanks for the awesome feedback. Iām currently writing a 3 part beginners series on react. The first part is already out here.
After that I wanted to do a vanilla JS article again š