Unexpected Joy From Functional Programming

Grooking Simplicity over Objective-oriented Programming

Sigrid Jin
11 min readOct 2, 2022
Image Reference: https://betterprogramming.pub/why-javascript-might-be-a-functional-programming-language-to-simplify-your-web-code-1eb271d04b1e

The paradigm change from the object-oriented to the functional paradigm was one of the most striking ones to date. I believe that many of us who spend a lot of time developing JavaScript solutions have begun to include functional programming methods in our daily work. I initially started doing it mostly on trust, following the logic of “if there’s smoke, there must be fire.”

I first found it helpful to reduce some boilerplate code. For example, I no longer make variables very often. I use an array.forEach(() =>) instead of a “for (let I = 0; I 100; i++)”. In fact, the entire “for (let i=…)” construct is frequently used to exclude items from a collection or perform some type of modification on items in a collection.

Why should you learn functional programming?

Developers always hope that the code they develop is sound, and continuously reflect on the notion. There are no definite criteria for excellent code, but one does know that the more structurally planned the code is, the better it is. The difference between excellent code and terrible code is whether the code you’ve written is hindering or assisting you.

It is difficult to differentiate between excellent and terrible code in a tiny-sized application. Instead, it is because code that does not is more efficient. However, once the size of a program exceeds a certain threshold, code without design becomes less productive. Therefore, we must strive to preserve the excellent design.

The reason I say maintaining excellent design is that good design is not a one-time activity but should be created with consistent rules and principles throughout the code. We refer to these beliefs and views that act as approaches as paradigms.

Object-oriented programming is a paradigm in which programmers think and write about objects; thinking and programming that focuses on linking data with functions are known as the functional programming paradigm.

Multi-paradigmed JavaScript

Even before it was given its name, JavaScript was a useful programming language! The Netscape Browser’s creators, Netscape Communications, understood in 1995 that their browser required a scripting language that was incorporated in HTML. To include the Scheme programming language in HTML, they recruited Brendan Eich. A complete functional programming language, Scheme.

To Brendan’s dismay, the powers that be pushed him to develop a more conventional programming language. His first release was a language in May 1995 that went by the name Mocha and remained under that name through December 1995. Following a licensing deal between Netscape and Sun, it was initially renamed LiveScript and then given the moniker JavaScript. Brendan Eich, however, had already snuck certain “functional programming” elements into JavaScript at that point.

It may have been a bait-and-switch tactic, according to Brendan Eich, who claimed to be hired to embed the Scheme language. However, Netscape was also talking with Sun Microsystems at that time to include Java into the browser. Notably, Java was used for browser embedding whereas JavaScript was used for HTML embedding. Java was to be utilized for component development, while JavaScript was to be used for HTML’s lightweight scripting.

JavaScript does indeed resemble Java and C as well. However, JavaScript’s ability to “look like Java” meant more than just curly brackets. JavaScript has to be written in an imperative/object-oriented approach. Does JavaScript an imperative, object-oriented, or functional language?

The language uses many paradigms. Not all of its functioning elements are present. But it is making progress. The majority of other languages likewise share this trait. The majority of languages — aside from functional languages — have gradually gained functional elements to varying degrees. The Array.forEach function is a prime illustration of JavaScript’s multi-paradigm nature. Here is a hypothetical straightforward application. Note that this has already been included by all contemporary browsers.

Array.prototype.forEach = function(callback) { 
for (var i = 0, len = this.length; i < len; ++i) {
callback(this[i], i, this)
}
}

The forEach section of the code in the example above is imperative. It is object-oriented to add to the array prototype and use it in this way. Higher-order functions, a characteristic of functional programming, are functions that may be sent as arguments to other functions under the name callback functions. One would provide function as an argument in JavaScript because the language assumes that the function is the first-class citizen. Surprisingly, until recently, the majority of widely spoken languages did not have this trait. In Java, for example, functions cannot be explicitly sent as arguments, but interfaces may. The same is true for C, albeit pointers may be used to do it indirectly.

What would happen if JavaScript did not have the ability to provide functions as parameters? One would have to repeatedly create the same for-loop every time we needed to traverse over an array. If Array.forEach exists, all we would have to ponder about writing codes is the callback. You don’t need to worry about iterating across the array; we simply need to consider what to do with each entry in the array. In other words, we have abstracted the problem’s iteration component in order to focus on each member of the array.

Programmers may concentrate on the issue at a higher level by using abstractions to hide implementation specifics, reduce, and factor out details. Code abstractions and data abstractions are both types of abstractions, as we saw in the example above. Code and data abstractions are combined into a single abstraction called a class in object-oriented programming. To abstract away code and sometimes even data, functional programming uses concepts like first-class functions, nested functions, anonymous functions, and closures. Functional programming’s unusual monads feature may abstract away program structure.

Action, Calculation, and Data

Pure-function and side-effect definitions are often the first step in functional programming, which is believed to synthesize pure functions while avoiding side-effects.

You would discover explanations of side effects such as DOM modifications, logging, reading and writing to files, communication with the server, sending mail and more. if you think, “Well, that’s a nice term…” and take a closer look at the side effects.

Naturally, the next idea is “No? Without parsing the DOM, reading and writing files, or interacting with the server, how are you expected to program? Looking at example programs that don’t seem useful at all and the theory of functional programming that grows more and more complex as I think about it, I often finish up at this level with the sense that functional programming is acceptable to learn later. — Teo

Functional programming has the principles of utilizing pure functions for the sake of being free from side effects and modified states, however, your programs would have meant to have side effects (to modify states). Your software is useless if a method cannot modify the files or logs. The application cannot be created by pure functions alone.

Let’s redefine functional programming as programming by classifying programs into three primary groups: action, calculation, and data, as opposed to using a phrase that leads to misconceptions.

  • Data: Data comes first because it is the most straightforward. Facts concerning occurrences are data. It’s everything transparent and inactive, such as integers, strings, collections of those, and so on.
  • Calculations: Calculations include conversions between input and output. Given the same input, they always provide the same result. That implies computations are independent of the timing or frequency of their execution. Since of this, they don’t show up in timelines because the sequence in which they run is irrelevant.
  • Actions: Executable code that affects or is influenced by the outside environment is called actions. That implies that they are dependent on when or how often they run.

An example abounds in the following code snippet that increments the basic counter variable. The snippet was referred from here.

<button id="button">0</button>

<script>
document.getElementById("button").onclick = function() {
button.innerText++
}
</script>

Facts concerning occurrences are data. Numbers are data that may be seen. The result of an activity depends on when and how often it is carried out. The number increases by 1 when the user clicks the button since the results have varied depending on how many times you do something and when you do it.

An output value is produced by calculation from an input value. When you click on the button, it adds 1 to an existing number to get a new number. When an action is taken, each relationship in the program modifies data in accordance with a predetermined computation.

function App() {

// Data
const [count, setCount] = useState(0)

// Cal
const increase = (value) => value + 1

// Action
const onClick = () => setCount(increase(count))

// Declarative Pattern
return <button onClick={onClick}>{count}</button>
}

Rewrite the program as above using functional programming methods.

Calculations must employ both input and output, and the same outcome must always be given for the same input. The results of the calculation should remain unchanged even after multiple iterations. Since it changes depending on how many times it is used, the increment function used in the example above seems to be a calculation when it is really an action.

The main objective of functional programming is to manage actions by clearly separating them from calculations in order to minimize actions and generate a lot of calculation functions.

const count = 0const increase = () => {
...
const result = count + 1
setState(result)
...

return result
}

const action = () => {
...
increase()
}

Input and output are inherent to all functions. Due to JavaScript’s extreme freedom, implicit input and output may exist alongside explicit inputs and return values. The variable count is an implicit input in the code above since it comes from outside the method. The implicit value of 1, which is immutable, is assigned to the variable result. The implicit output of the setState function modifies a value outside of the function.

As above, a function that combines implicit and explicit I/O is an action rather than computation. Since actions rely on the quantity and time of executions, testing them may be challenging. A function that combines action and computation has to have the calculation portion separated. enables you to get rid of implicit I/O. This method now makes calculations independent. Calculations have no side effects and only have stated inputs and results. The same input should consistently provide the same output.

1. Make explicit inputs for the count and offset.
2. Avert output that is explicit, like setState method.
3. A totally different action method is used to gather implicit I/O.

const increase = (count, offset) => {
const result = count + offset
return result
}

const action = () => {
setState(increase(count, 1))
}

A program is an activity that modifies data; in other words, a program modifies data. The functional programming paradigm connects actions, computations, and data via functions in a manner that allows data to change independently of actions.

To make complicated code simpler and more testable, it’s crucial to isolate independent calculations from actions that rely on runtime and count.

Separate the code into actions, computations, and data using pure functions. Create code that clearly distinguishes the calculation from the action.

Immutability

My practical definition of immutability is, if you don’t modify something, there’s no code that could possibly modify it, then it is effectively immutable. It’s a discipline. You could break the discipline. You could mess up and do something wrong, but it’s an ideal to strive for. It is not as hard as you might think, as a discipline. — Eric Normand

There are really two and a half basic types of discipline used to maintain immutability.

const increase = (arr) => {
arr = arr.slice() // clone array before modifying it
const value = arr[arr.length - 1]
arr.push(value + 1)
return arr
}

// using spread syntax to concisely code the method
const increase = (arr) => [...arr, arr[arr.length - 1]]
// no need to modify the original to add new name: value
const setObjectName = (obj, value) => {
return {
...obj
name: value
}
}

const setObjectName = (obj, name) => ({...obj, name})

One is copy-on-write, where each time you wish to edit a data structure, you create a copy of it and then modify it. You cannot edit it after you have done the modification and have released it outside of your scope, sent it back, or given it to someone else or another portion of the code. Nobody is able to alter it. Making a duplicate of whatever it is you wish to edit and then making the change while only you have control of that copy is how you change anything.

import someActionLibray from "lib"

const someCalcuation = (obj, value) => {
someActionLibray(obj, value)
return obj
}

Copy-on-read is the name of the second way. Sometimes you have to work with a library, old code, or other unreliable material. Perhaps you put your confidence in it for the incorrect action. It affects things, you know. The copy-on-write process is not used. Your data structures and everything else it touches is not immutable since it does not perform copy-on-write.

You can’t rely on a data structure to be immutable if you call an API function or library function and receive a data structure back. Take the example above. You could convert the action to a computation using the Copy-On-Write technique once discussed above, but what if the action is a library that we are unable to alter? A computation function becomes an action if it has at least one action. Otherwise, the actions that are produced will spread across the code, making it challenging. What should one do in this circumstance?

// https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
// https://github.com/zloirock/core-js#structuredclone
const someCalcuation = (obj, value) => {
const clone = structuredClone(obj)
someActionLibray(clone, value)
return clone
}

Rendering a deep duplicate is the first thing you would do after receiving the library API — making a detailed duplicate of it. You don’t have to worry about what this library internally does with the item it handed you. Simply dispose of it. When you say, “It’s in my possession; it’s a copy. I’ll handle it in my own manner.”

You may now use immutable data again with confidence. All of this is to argue that there is a second response to the issue, “Does immutability require functional programming?”

No. Even changeable structure may be implemented on top of it. Mutable objects may be used even on top of mutable HashMaps. You are good as long as you are able to produce duplicates, both shallow and deep. Functional programming is a thing you can accomplish even if your language does not include immutable data structures or a means of ensuring immutability. These copy-on-read and copy-on-write disciplines may still be used to create immutability.

Remarks

You can see that a hierarchy among functions is organically established throughout the process of putting these segregated programs together. You can automatically develop a decent code structure if you separate the code into actions, computations, and data, construct hierarchies and write code that does not cross hierarchies. A solid foundation for effective design and refactoring may also be created by raising the computational weight and splitting the code so that it doesn’t traverse layers.

Reference

--

--

No responses yet