DEV Community

Cover image for Why do we write JavaScript like this?
Anders
Anders

Posted on

Why do we write JavaScript like this?

Before we start, I do realize that this may potentially come off as a little harsh or negative, but that is not my intent, I am just curious as to why we’ve ended up writing code this way? All the samples in this article are from this article: https://dev.to/papabearcodes/four-common-javascript-interview-coding-problems-1gdk.

Over the past several years I’ve been reading a lot of articles here on Dev.to and I just keep coming across JavaScript that looks like this:

const isPalindrome = string => {
  let revString = [...string].reverse().join('')

  const palindromeCheck = () => string === revString ?  true : false

  return palindromeCheck()
}

And I can’t help but wonder, why do people prefer writing code like this. It makes use of a lot of built in functions, lambdas and what not, but it's also perhaps a little more difficult to read than you’d like, and if you think about what it's actually doing, it's not very efficient.

Let’s break this one down:

We use the spread [...string] function to make the string into an array of characters (memory allocation), we then reverse that array (hopefully this at least is done in place by the compiler, but I doubt it), lastly we join that back up into a new string (memory allocation).

Second we declare a sub function that basically just performs a single comparison, this literally seems to have absolutely no purpose whatsoever.

I’d personally write the thing something like this:

function isPalindrome(s) {

  for (var i = 0; i < s.length / 2; ++i) {

    if (s[i] != s[s.length - 1 - i]) {

      return false;
    }
  }

  return true;
}

Certainly a few more lines, and very “old looking” perhaps, but I am old ; ). Thing is though, this runs faster (albeit slightly), and in my mind at least is also easier to read and understand. But maybe I’m just crazy.

This example also sticks out, from the same article:

const sum = (...args) => [...args].reduce((acc, num) => acc + num, 0)

Granted, it is actually preceded by a more readable alternative first, that again avoids the things that I just think you should always avoid, poor readability.

function sum() {
  let total = 0

  for (let num of arguments) {
    total += num
  }
  return total
}

There is no way you’d ask yourself how that works.

However that example uses the “of” operator on the arguments array, a “new” fun feature. Sadly, performance is affected, using jsbench.me to compare the two the one line version is massively faster, the second version is reported as 92% slower than the first. This probably doesn’t matter for a function that runs once or twice but if we keep following this kind of pattern of blatant disregard for performance we will get into a lot of trouble eventually (Some may even say we are already, I may be one of those people ; ).

I did my own take as well, old school javascript again:

function sum() {

  var sum = 0;

  for (var i = 0; i < arguments.length; ++i) {        

    sum += arguments[i];
  }

  return sum;
}

Super simple, also as it turns out, super fast, our old one line champ is reported as being 74% slower than this above function.

Rounding up, let’s take a third function:

const captilizeAllWords = sentence => {
  if (typeof sentence !== "string") return sentence

  return sentence.split(' ')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ')
}

Split, Map, Slice, Join.. so many array operations in action here. Let’s try a more basic approach:

function captilizeAllWords(s) {

  var capitalize = true;
  var returnValue = '';

  for (var i = 0; i < s.length; ++i) {

    var c = s[i];

    if (c == ' ') { 

      capitalize = true; 

    } else if (capitalize) {

      c = c.toUpperCase();
      capitalize = false;
    }

    returnValue += c;
  }

  return returnValue;
}

This is a much more explicit take. Again it’s slightly longer. And again it's faster. JSBench reports the first variant as 33% slower here. An additional benefit of this solution, if you actually wanted to do this for things after a tab or new line, that doesn’t really take much thinking of code to achieve.

That does it for this old developer ranting about new stuff style article, hopefully this can lead to some healthy discussion if nothing else =).

Eat your vegetables, make your code readable, and don't forget to BENCHMARK!

Top comments (65)

Collapse
 
tiagojpdias profile image
Tiago Dias

I don't want to sound bitter but...

I don't get the idea of writing convoluted examples to force your point of view...

How is this hard to read?

const isPalindrome = (actualString) => {
  const reversedString = [...actualString].reverse().join('');

  return actualString === reversedString;
}
Enter fullscreen mode Exit fullscreen mode

And why would you rather write code like this?

function isPalindrome(s) {

  for (var i = 0; i < s.length / 2; ++i) {

    if (s[i] != s[s.length - 1 - i]) {

      return false;
    }
  }

  return true;
}
Enter fullscreen mode Exit fullscreen mode

Single character variables? s.length -1 -i ? Creating if statements to return true/false ?

How s this more readable to you?

Collapse
 
anders profile image
Anders

This is exactly the discussion I want us to have =)

as for the particular example you highlight (and I do take your point about "s", although "i" as a common name for an iterator.. well, its quite common)

In my view what you can see there is code that more explicitly mirror the actual functions "action", specifically it explicitly does what you'd do to see if a string is a palindrome or not, ie, you check the "mirror" character to equal the current character.

Using an if statement for an early out I feel also is quite handy, as soon as its apparent that its not a palindrome, we return, without having to process the entire string in those cases.

Collapse
 
functional_js profile image
Functional Javascript

Choosing implementation variants is straightforward if you adhere to a predefined criteria set.

I write about how to evaluate implementations on criteria here:
TLDR: Choose the fastest implementation considering equal robustness.
dev.to/functional_js/squeezing-out...

In this case, the loop is many magnitudes faster. Use the loop.

//a.
const isPalindrome_loop = str => {
  const s = str.toLowerCase();
  for (let i = 0; i < s.length / 2; ++i) {
    if (s[i] !== s[s.length - 1 - i]) {
      return false;
    }
  }
  return true;
};

//b.
const isPalindrome_spread = str => {
  const s = str.toLowerCase();
  return [...s].reverse().join("") === s;
};

//@tests
const palid = "A man, a plan, a canal: Panama—!@#$#@%$$%^&&^*()=-_+,.<>/?";

//a.
timeInLoop("isPalindrome_loop", 1e6, () => isPalindrome_loop(palid));
// isPalindrome_loop: 1e+6: 162.986ms

//b.
timeInLoop("isPalindrome_spread", 1e6, () => isPalindrome_spread(palid));
//isPalindrome_spread: 1e+6: 5.494s
Thread Thread
 
anders profile image
Anders

Very clear comparison of the actual performance of the two.

I do wonder though why declaring the actual functions as "function Name(arg) { ..." has fallen somewhat out of favor, it just seems such a more natural way to do it given how the English language works.

Then again in most other languages you'd typically see:
returnType FunctionName(args) {
or something similar to that

Thread Thread
 
functional_js profile image
Functional Javascript

That's a good question, here's my answer to it:

I don't understand why people still use the "function" keyword, unless you are accessing the "this" keyword, which should be never, except for legacy JavaScript.

Plus I would pick one consistent strategy and go with it, instead of intermingle, "sometimes arrow and sometimes function".

I also don't use single quotes. Everything double quotes.
Then there's none of this, "sometimes this, sometimes that" sprinkled all over the place.

I'm a minimalist, and only use a subset of the language.
Here is a list of what I don't use in JavaScript:
dev.to/functional_js/what-subset-o...

Thread Thread
 
anders profile image
Anders

Thanks for that response.

I agree, consistency is key. Which is why on my projects there are always a set of coding guidelines that are enforced in terms of naming, spacing, comments, all that.

I do use the "this" keyword relatively frequently however myself, specifically from within member functions of an object, to access member data or other member functions.

Thread Thread
 
functional_js profile image
Functional Javascript

That's because of your Object-oriented approach to programming.

I use a functional approach, so everything in that list I linked to in my last post are unnecessary.

Without those constructs, there is almost nothing left but funcs themselves, specifically, arrow funcs.

Collapse
 
brandinchiu profile image
Brandin Chiu

In this particular case, the if is redundant, as the condition for your if is the same as your return content.

You could simply return everything between the brackets of your if instead of explicitly returning true.

The above is important because the way you've written actually makes your application harder to read because it increases what's called "Cyclomatic Complexity" by introducing a fork in your execution path.

Other than that, "the syntantic sugar" of Javascript (arrow functions and the like) are not something I like either. What you're seeing is the proloferarion of the "Code Golf" fallacy. While a fun thought experiment, it is often a measurably worse way to write software thay needs to be maintained by other people.

Thread Thread
 
anders profile image
Anders

I'm not entirely sure I follow, how would that look?

I do agree in principle though re: complexity. I could have spent some more time on my counter examples for sure.

Thread Thread
 
brandinchiu profile image
Brandin Chiu • Edited

Oh woops!

I've misinterpreted what this is doing; I thought I saw an else statement in your if.

That's definitely my bad -- ignore my comments about Cyclomatic Complexity. They don't apply here.

Thread Thread
 
anders profile image
Anders

Roger that, I thought you had some extra magic in store there and got way intrigued : )

Collapse
 
cwraytech profile image
Christopher Wray

That was my thought too. The second is much less readable.(:

Collapse
 
vonheikemen profile image
Heiker

Peer pressure? Almost everyday I read an article that has the following:

Imperative code is messy and error prone.

You should avoid mutability at all cost.

Don't use var. Don't use for loops.

Their solution? Writing in the way you describe in this article. And this is the kind of content that influence new developers. Some may end up obsessed with immutability and "being declarative" to the point they don't consider performance at all.

Collapse
 
wulymammoth profile image
David

The responses that you quoted are valid, but difficult to stand on their own. I much much prefer declarative code -- it does what it says. In the context of JavaScript, particularly in browser, the performance argument is debatable.

Any extremes without considering BOTH readability AND performance, will make someone unhappy. On the other extreme if you optimize for just performance, recursion should never be used -- everything should be written with imperative and iterative code. On this extreme, we have the competitive programmers with single name mutable variables and no spaces between operators -- compact, concise, and performant.

Cargo culting is bad if people don't change their perspectives given new information. I think it's fine that newcomers come in and stand on one side first without having to be paralyzed by having to think about both, but for an experienced dev, I'd imagine the expectation to be different and in mixed paradigm languages, like JavaScript, things aren't mutually exclusive (we can have both), but in a functional language -- ALWAYS use declarative code as the VM will optimize it anyway (e.g., tail-call optimization, and immutable data-types actually reference the same address in memory)

Collapse
 
pentacular profile image
pentacular

None of the examples shown are declarative.

I think people are somehow confusing point free code with declarative code, which is quite different.

You can put the points back in to make this obvious.

Thread Thread
 
wulymammoth profile image
David

I think you're right -- I mean the functions themselves are not, but the devil's in the details... as I would consider the use of reverse to be declarative within the body of the function.

[via Wikipedia]

In computer science, declarative programming is a programming paradigm—a style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow.

There is no logic expression that describes control flow in the use of the reverse method.

That said, I don't believe point-free code and declarative code are mutually exclusive -- SQL comes to mind

Thread Thread
 
pentacular profile image
pentacular

With that definition, all procedure calls are declarative, which seems incorrect to me.

A declaration describes the state of the universe, a procedure call makes things happen.

What things it makes happen might be hidden from you, but the call itself is an explicit operation at a point in time, containing those hidden operations.

Thread Thread
 
wulymammoth profile image
David • Edited

With that definition, all procedure calls are declarative, which seems incorrect to me.

I see what you're getting at. I'm really just talking specifically about the method, reverse, afforded by arrays in JavaScript. I'm not talking about the body of the function which is what I believe you to be interpreting here. This is why I agree with you that the examples used are not declarative.

However, I'm wondering how you would describe the difference between the following, because to me, the first example, in my opinion, is imperative and procedural:

(function(nums) { 
  let sum = 0;
  for (let i = 0; i < nums.length; i++) {
    sum += nums[i];
  }
  return sum;
})(numbers);

// versus
numbers.reduce((sum, num) => sum + num); 
Enter fullscreen mode Exit fullscreen mode

The second example says what we're doing rather than how we do it: "reduce the numbers to a sum total", whereas the first example's body has order dependence and is imperative that we declare and initialize s before looping across the input numbers and then adding to s...

Like, how would you describe the "sum" operation in the absence of the term, "declarative"?

A declaration describes the state of the universe, a procedure call makes things happen.

[via Wikipedia]

In computer programming, a declaration is a language construct that specifies properties of an identifier: it declares what a word (identifier) "means"

I think you're conflating declarative programming here with a declaration.

// standard declaration (uninitialized)
let foo; 
Enter fullscreen mode Exit fullscreen mode

I'm really just honing in on the paradigm itself and how operations are expressed -- functions that say what we're doing as opposed to how we do it in the spirit of how "declarative programming" is often understood and described

Thread Thread
 
pentacular profile image
pentacular • Edited

The only difference that I can see is that you've exposed the guts to view in the first.

Let's put those guts elsewhere and see how it looks now ...

sum(numbers);

// versus
numbers.reduce((sum, num) => sum + num);

// Or we can make the similarity more obvious.

Array.reduce.call(numbers, (sum, num) => sum + num);

I think you're conflating declarative programming here with a declaration.

Declarative programming is programming by the assertion and retraction of declarations.

Consider a classic declarative programming language like prolog, for example.

reverse([],[]).
reverse([H|T], RevList):- reverse(T, RevT), conc(RevT, [H], RevList).

What we're doing here is declaring what makes something the reverse of something else.

This declaration allows the system to accept that reverse([1, 2], [2, 1]) is valid.

It also allows the system to infer that X must be [2, 1] given reverse([1, 2], X).

Or equally that Y must be [1, 2] when given reverse(Y, [2, 1]).

Or more generally constrain X and Y such that they are the reverse of one another given reverse(X, Y).

You may note that we're declaring relationships here rather than instructing the system to make procedure calls -- and what I see in your examples is simply instructing the system to make procedure calls, and a claim that when the content of the procedure is unknown, it is somehow now declarative.

Thread Thread
 
wulymammoth profile image
David • Edited

Right -- but hoping that you prefer the second, how would you describe the former and the latter? To say, "you're exposing the guts" versus, "let's not expose the guts" just doesn't seem very formal granted that we're having a discussion about semantics.

Declarative programming is programming by the assertion and retraction of declarations.

I've never quite heard it described in this way, but really what I'm trying to get at is how you would describe the different styles if not "declarative". It's fine if you have a different interpretation of it, but I'm operating under the widely held notion of what it is. My example is nearly identical to the answer here described on Stack Overflow: stackoverflow.com/a/17403998

[from Wikipedia for "declarative programming"]

In computer science, declarative programming is a programming paradigm—a style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow

I love ML flavored langs and I also enjoy functional languages, so the example is neat, but in a language devoid of such constructs, let's revisit the original example from OP in JS:

// imperative
function isPalindrome(str) {
  let left = 0, right = str.length-1;
  while (left < right) {
    if (str[left] !== str[right]) return false;
    left++; right--;
  }
  return true;
}

// declarative
function isPalindrome(str) {
  return str === str.reverse();
}
Enter fullscreen mode Exit fullscreen mode

Your example with Prolog is neat, it's much more expressive of the relationships between arguments. I'm very familiar with this as I write Elixir and some Erlang. But I hesitate to describe declarative programming somehow boils down to: 1) requiring a declarative programming language, and 2) a declaration of relationships, to be declarative programming -- the Wikipedia text would need to be edited if so. It's never been described to me this way and I've never read any formal texts defining it in the manner that you have. However, I could certainly still be wrong. BUT, I'm willing to reconsider in the presence of formal literature/texts. I can be pretty pedantic, so I care about this and am willing to be enlightened :)

Thread Thread
 
pentacular profile image
pentacular

Right -- but hoping that you prefer the second, how would you describe the former and the latter? To say, "you're exposing the guts" versus, "let's not expose the guts" just doesn't seem very formal granted that we're having a discussion about semantics

My point is that your examples are identical except for having hidden or not hidden the implementation of the procedures from immediate inspection.

The consequence is that your definition of 'declarative' becomes 'a procedure with a hidden implementation'.

Thus making all procedural programs declarative by simply moving the bodies of the procedures into a hidden library somewhere.

This means that the examples can be transformed from procedural to declarative and back again depending on where the procedures you call are defined.

Which I hope is sufficiently absurd as to demonstrate that your example either (a) does not demonstrate your definition, or that (b) your definition is wrong. :)

Take it as an argument by contradiction.

As for a formal definition, taking a little look around, I find a reasonable over-view here.

inst.eecs.berkeley.edu/~cs61a/sp14...

So far, our programs are explicit directions for solving a problem;
the problem itself is implicit in the program.
Declarative programming turns this around:

This gets to the central concept.

In a declarative system, we describe the problem in sufficient detail that a solution can be automatically discovered -- the problem is made explicit; the solution is implicit.

In an imperative system, we describe the solution to a problem in sufficient detail that an answer can be automatically computed -- the solution is made explicit; the problem is implicit.

numbers.reduce((sum, num) => sum + num);

Ignoring the fact that syntax expresses an eager procedure call in an imperative style ...

Are you describing a problem or a solution here?

Thread Thread
 
wulymammoth profile image
David • Edited

My point is that your examples are identical except for having hidden or not hidden the implementation of the procedures from immediate inspection.

Hard disagree -- please, do not try to shoe-horn this statement into a discussion about imperative and declarative programming. That statement is what's described as an abstraction.

The consequence is that your definition of 'declarative' becomes 'a procedure with a hidden implementation'.

Thus making all procedural programs declarative by simply moving the bodies of the procedures into a hidden library somewhere.

This is very reductionist -- by your definition, style isn't even relevant, and "expressiveness" matters little. Case in point -- recursive function to produce the n-th Fibonacci versus an iterative non-recursive Fibonacci function. They both produce the same result. The recursive version makes excruciatingly explicit the relationships between arguments whereas the iterative version is less so being decorated with mutable variable set-up and a boilerplate loop.

This means that the examples can be transformed from procedural to declarative and back again depending on where the procedures you call are defined.

You may believe this to be absurd, but you have to understand that this is your and specifically only your worldview. I will say, though, if you go back up and visit the Stack Overflow link, there will be people arguing similarly to you, but it is in the minority. If you're looking to relish in your own definition, it's a worthwhile visit. Me? I try not to engage in that behavior...

That said, thank you for sharing a link to my alma mater -- what you've quoted actually supports my original point more than yours:

  1. "our programs are explicit directions for solving a problem; the problem itself is implicit in the program" -- simply read as, "step-by-step procedures to produce a desired result for a given problem"
  2. "declarative programming turns this around" -- read as, "result to the problem"

I'm unsure how you read that differently than I. You're absolutely correct that the quote you've shared gets at the "central concept" as you've described. It re-iterates every single response that I had shared previously

In a declarative system, we describe the problem in sufficient detail that a solution can be automatically discovered -- the problem is made explicit; the solution is implicit.

I'm going to highlight that you're literally building your own worldview -- this is your "system", whereas I'm talking about a paradigm and/or style that I take no credit for. There are no hard boundaries on such things only a generally agreed upon subjective (yeap) blurry/fuzzy understanding of it. This explains why there's people going back and forth on it on the Stack Overflow thread like you and I are. But the generally agreed upon idea about what declarative (style [not your system]) is holds true.

Your highlight of my example employing reduce and labeling it imperative is just plain wrong IMO. I'm going to hard disagree here. By that measure, writing a SELECT query in SQL is imperative style. Your line of reasoning is orthogonal to what is written both in Wikipedia and academic literature (the one that you've shared) from the EECS program at UC Berkeley.

I'm going to hard disagree with your assertions that my examples are wrong. It may not speak to you, but you've not managed to change my mind. The only thing I got was someone trying to push their worldview on someone else, illustrated in you saying that my functions are the same. Yes, they produce the same result, but they are written in a completely different style and "imperative" versus "declarative" is about style, and I think you'd be hard pressed to find someone disagreeing with the following being an adequate idea about what the differences between the two styles are:

// you literally have to read the entire procedure to understand what's going on
// discoverability is here, but at what cognitive cost?
function main() {
  let left = 0, right = str.length-1;
  while (left < right) {
    if (str[left] !== str[right]) return false;
    left++; right--;
  }
  return true;
}

// declarative - what's the cognitive cost?
function main() {
  return str === str.split('').reverse().join('');
}
Enter fullscreen mode Exit fullscreen mode

If they're the same, you've just contradicted yourself. The second version literally describes the relationship that you went out of your way to highlight to me in a roundabout way to try and impose your worldview. Which one more so exhibits the "...sufficient detail that a solution can be automatically discovered..." (literal quote)? I'm going to come back to the idea that you didn't show a preference for the latter, because it wouldn't support this world that you've found yourself in that you've created for yourself. I'm willing to bet that most people, and especially beginners would prefer the latter to describe what a palindrome is rather than the former, but you wouldn't admit that, would ya? If they're the same, I beg not to program or review code written by those that see no difference. Maybe I'm dumb and lazy, but I prefer not to need to signal my smartness by telling others that I can read through cryptic expressions in the presence of alternatives.

That said, you've not convinced me beyond the shadow of a doubt that my understanding is incorrect, but I'll acknowledge and admit that you've made it abundantly clear to me that an alternate view of the paradigms (not your system) exists.

My final points (rhetorical questions):

  • why invent DSLs or SQL since we can all write line-by-line instructions for the machine?
  • why have any methods or functions in programming languages at all?
  • why don't we just dump everything in a single file?
  • why do programmers concern themselves with style?

Imperative and declarative programming are really about style and why do programmers care? The explicit reason is expressiveness - does it adequately describe and express what we seek to accomplish AND/OR the relationships between arguments/inputs such that I don't have to concern myself with the details at a granular level. The latter is more in line with what you've suggested, but they are not mutually exclusive. In fact, functional programming languages, in which some of have its roots in lambda calculus is all about this as I'm fairly sure that you're aware of. Functional programming in most languages, when we squint, look just like formulas with a label slapped onto them... a declaration, so to speak

Thread Thread
 
pentacular profile image
pentacular

Let's consider your reduce example.

I think you've claimed that this example is declarative?

const result = array.reduce(add);

Is this example also declarative?
I think that you'll have a really hard time justifying a 'no' here, but let's see. :)

const result = reduce(array, add);

How about this one?
You've classified this kind of thing as non-declarative earlier, so it'll be interesting to see your decision this time.

const reduce = (array, reducer, state = 0) => {
  for (const item of array) {
    state = reducer(state, item);
  }
  return state;
}

const result = reduce(array, add);

Or this one?

const sum = (array) => {
  let total = 0;
  for (const item of array) {
    total = add(total, item);
  }
  return total;
}

const result = sum(array);

And one last one -- declarative or not?

const result = ((array) => { let total = 0; for (const item of array) { total = add(total, item); } return total; })(array);
Thread Thread
 
wulymammoth profile image
David
  1. Yes
  2. Yes
  3. reduce is implemented in an imperative style, but the usage is declarative
  4. Same as 3 — implementation is imperative and usage is declarative
  5. Imperative

The result statements each have a declarative expression, but your reducers are written in an imperative style. Pretty much all your examples are declarative with the last example as the exception, where you’ve baked an imperative reducer into the IIFE. This is precisely why I state that it isn’t mutually exclusive in a paradigm-less or mixed paradigm language like JavaScript. Functional programming to functional paradigm is declarative programming to declarative style.

I don’t think you’d disagree that all your result statements simply look like assertions of the relation between the what’s on the left hand side and what’s on the right hand side (like in math). At some point or another the implementation whether remaining in the domain of a high level language or not will evidently have imperative code or instructions (the procedures). Some languages make it easier to express things in strictly one paradigm.

This whole discussion could’ve been about whether JS is a functional language. Where we disagree is really on premise — you’re talking about purity and I’m not. Just like the folks that would argue against Scala being a functional language. But here I am stating that it just has functional facilities, but yet someone refuses to hear me and assumes that what I’m asserting is that the language is functional. This is absurd. Lol.

Just let me be wrong if it’ll make you feel better. I’m not here to stroke anyone’s ego. I think it would’ve saved both you and I time if you gave examples of declarative JS from the outset. But scrolling back, I can’t find a definitive example illustrating the stark difference between imperative and declarative JS in your eyes. You went and made an attempt to elucidate with Prolog that is a classic declarative language. It would help many others if you showed us (even contrived examples) of declarative JS rather than speaking in the abstract.

Show us the light, Yoda! For we are all padawans.

Thread Thread
 
pentacular profile image
pentacular

Thanks -- that was useful.

The only difference between the examples was the degree of procedural abstraction.

Which backs up my theory that that is what is being confused with declarative programming.

This could have been about if JS is a function language if you wanted to confuse declarative programming with functional programming, but that would be regrettable in my opinion.

Although it's understandable, since a functional style in a procedural language is mostly about leveraging procedural abstraction.

If you can find an example of declarative programming in JS that isn't actually just procedural abstraction, that would be great.

Something which, for example, declares the problem to be solved, rather than specifies how to solve it, regardless of how much procedural abstraction that solution involves. :)

At this point I'm interested in understand how some people have come to think of declarative programming like this -- once it is understood it may be clear how to correct it.

It concerns me because, much as "moron" was once a meaningful technical term that has been reduced to a meaningless schoolyard insult, it seems that "declarative" is being watered down into something similarly meaningless, and I find this disappointing.

Thread Thread
 
wulymammoth profile image
David

I mean, a functional style doesn't mean it's not "functional" (programming)...

The only difference between the examples was the degree of procedural abstraction.

I don't disagree here. But hey, we're not all mashing machine code, so we're all operating at one abstraction (as leaky as they may be) or another.

Furthermore, specificity matters only in particular contexts. As human beings, we can't help but make associations -- we constantly hang leaves upon existing branches (even if seemingly the wrong one to someone else at the time). And while on the one hand, precision matters... but mostly when that precision would result in the wrong result or idea. This is why I'm so annoyingly pedantic at times. But here, in the absence of a succinct phrase to describe the difference in writing boilerplate code versus non-boilerplate code, I believe "declarative" (in spirit) is an apt way to describe what's going on if we are in agreement that code readability is the context. At least it's a commonly used way. I don't think we need to course correct here, but rather people familiarize themselves with etymology, such that it avoids misrepresentation in the wrong context.

While I agree with you on both "declarative" and "moron" now, we must understand that language, too, evolves. I think it's a healthy mindset to also be an observer as it evolves and step in from time to time, like you have, to provide some historical context. It's hard, I know, as someone who used to frequently argue with people about the "correct understanding" or "correct meaning" of something, before someone told me to not be so one-dimensional and accept both. Funny, but it's definitely made things easier in some respects, though, more complex and/or intricate which we often fight against. But hey, we're engineers/developers and if we don't do it, we shouldn't expect others to do it, too as we continue to abstract over a complex world

Thread Thread
 
pentacular profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
pentacular

At this point you can't speak meaningfully about declarative programming. :)
How long before you can't talk about functional programming?
Until everything is one vague blur.
Spinning in an echo,
Chamber?

Thread Thread
 
wulymammoth profile image
David • Edited

Conflation is a valid concern, but if we believe the best in people, and there are almost always truth seekers among us, we will eventually discover the most minute of differences. We use analogies all the time and they’re okay even if not perfect analog so long as we acknowledge that. It’s really how people learn.

Blurred concepts and terms will always have people to clarify them but I wouldn’t lump that into echo chamber-ism. It is such only for those that spend an entire career in one ecosystem and never step out of their comfort zone to have their fuzzy ideas challenged, conceptualized differently, or placed against in the context of a different domain.

When I think echo chambers, I think JavaScript and its immense cargo culting of front end frameworks and libraries. It’s almost taboo to speak out against React without hearing the common talking points and refrains that its large community of users have adopted to combat dissenters or people suggesting other libraries or frameworks. Dan Abramov of Redux/React and Facebook engineer spoke out against a YouTuber that kept making fun of Angular. I say this because at least with echo chambers it’s easier to spot them and avoid them.

We also can’t teach anyone that isn’t ready to learn, and we tend to forget that some of the smartest people are the most hard-headed and often feel attacked if something or someone challenges their long-established beliefs. It causes tons of cognitive dissonance and often bars them from further learning. I recently had a discussion asking my cousin, a former Google engineer and heavy JS user if he’s checked out SvelteJS or some of the other micro-libraries, and I simply got, “why subject myself to a lack of support?” (not verbatim). That’s classic “straw manning” (just because of this, then it must mean that). What he actually meant was, “I just need to build something and it’s what I’m familiar with and don’t want to spend the time considering all the options right now”. That would be a much better premise to start on and would be fine, but we all know that people don’t say that, because we don’t want others to misconstrue that as being lazy. I’ve been in enough of these conversations now to be able to read between the lines and avoid an entire discussion on trying to share something I find fascinating to someone else when they’re not looking to have me do that.

Collapse
 
pentacular profile image
pentacular

I recently had a discussion where people claimed that object values were references.

I asked the people if they could point to something in the ecmascript standard to support this, but there was complete silence, except from one person who said that they couldn't make sense of the standard.

I am starting to get the feeling that people are largely learning in an echo chamber of well meaning, but often fundamentally confused tutorials, and in many cases aren't functionally literate in CS, which may kind of explain what you're observing.

Collapse
 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

Yeah, I blame lack of CS literacy here. Object values are memory addresses. "References" are pointers. This misunderstanding stems from a lack of exposure to languages like C++/Rust, where you're forced to understand what pointers are and how they're used.

Thread Thread
 
johnkazer profile image
John Kazer

Makes me think of an interesting split of opinion or approach or maybe programming temperament.

Some folk focus on the logic of what they are trying to achieve, they like functions and hate pointers. Others like to know more about what the computer is actually doing, they love pointers and benchmarking.

I'm being simplistic but it made me wonder...

Collapse
 
anders profile image
Anders

Maybe that is part of the cause for sure, and personally I feel that is unfortunate.

There is certainly value to immutability, but demonizing basic, useful programming patterns in the process isn't the way to promote it.

Collapse
 
sargalias profile image
Spyros Argalias • Edited

It's about readability and understandability.

I never aim to write functional code. I aim to write what I consider to be the best code I can write.

First example

In the palindrome example, the function body is 2 lines long:

function isPalindrome(str) {
    const reversedStr = str.split('').reverse().join('');
    return reversedStr === str;
}

The function body of the loop version has 4 lines of code and a few more lines with braces. The volume of code to read is much higher, and you must read all of it before you understand what it's doing. Furthermore, you have to actually parse the logic in your head and see how it works, which I find much more difficult than the simpler logic in the functional example.

"reverse the string and check if it's equal to the original", compared to "loop through the string, for each index, grab the current character, check if the current character is equal to the character at the index from the opposite end of the string and return false if it isn't".

It just takes longer to read and understand for someone who is well-versed in both versions.

About performance: In many apps, it brings no benefit to optimise performance for simple things (premature optimisation). It's usually better to optimise for readability and only optimise for performance if needed.

Second example

I would have written the second example like this:

import {add} from 'ramda';

const sum = (...args) => args.reduce(add, 0);

Using a library, or coding your own versions of these utilities, is fine, since you'll reuse those functions in many places throughout your codebase (DRY).

Again, if you understand what this is doing (which just takes a bit of practice), this is instant to read and understand. It says, "for every item in the list, add it to the total, with the total starting at 0".

The for-loop version is fine, but again, you have to read every word and parse it to understand it.

And so on.

Edit: Fixed the code.

Collapse
 
_hhandoko profile image
Herdy Handoko • Edited

I'm not sure I understand, if this is a satire piece then I missed it completely.

All the examples provided, are far more readable than the author's own examples. Perhaps, it is because those operations are inspired by Functional Programming concepts, and has commonality across many different languages.

Putting performance aside (for now), these examples shows intent. It tells the 'what' rather than the 'how'. In one glance, I can tell what the first code is supposed to be doing, not so much with the for loop of the second one where I have to inspect and step-through the algorithm (in my head).

In my personal experience as well, for loop implementations need to be accompanied with comprehensive tests as it is easy to make a mistake in them.

Performance matters, but I do believe readability and correctness matters more.

I would have been fine if the focus of the article was simply on performance of conventional, imperative JS code. However, it should be acknowledged that readability and comprehension is way lower.

Collapse
 
anders profile image
Anders

The article posts a question "why", and I think there has been quite a few potential answers to that question.

Not sure where you see many issues with loops though, care to elaborate with examples? =)

Collapse
 
_hhandoko profile image
Herdy Handoko

Missed the reply notification 😬

Not so much of a problem, but rather that for loops carry higher cognitive overload, whereas map or filter is composable and easier to comprehend (e.g. treating it as data pipeline).

So there is definitely a tradeoff, readability vs performance.

Collapse
 
heyitsarpit profile image
Arpit • Edited

This post just reminds me how badly we need a better standard library in JavaScript. This is what I mostly miss from python.

Collapse
 
anders profile image
Anders

As someone who has never used python, do you have any specifics to share on how something like that would look and work? : )

Collapse
 
heyitsarpit profile image
Arpit • Edited
def isPalindrome(string):
  return string == string[::-1]

A lot of this stuff regarding string and list(array) manipulation in python can be just a one liner.

Thread Thread
 
anders profile image
Anders

without deep knowledge of how python works that is not intuitive for me sadly. Might be time to look at some python stuff to get better insight =)

Thread Thread
 
heyitsarpit profile image
Arpit

That's fine. The only issue is the unfamiliarity with [::-1] or [:n] or [start:end] syntax. This works like Array.slice() for reading lists and strings. It just says you want to capture the list/string between a start and end index. The negative one means read the string backwards.

Thread Thread
 
anders profile image
Anders

Looked it up just now as well, there are certainly some powerful capabilities there, including the stride thing, real nice.

There is a lot of room for ergonomic improvements in JS. But it will forever be hampered by the "compiler" being on the remote system.

Thread Thread
 
johnkazer profile image
John Kazer

From a functional point of view I find that Ramda makes a good case as a standard library. Has a good reverse function as example. But certainly not to everyone's taste I guess.
ramdajs.com/docs/#reverse

Collapse
 
dfockler profile image
Dan Fockler

I think it comes down to the idea that the less state and logic you have to manage the less bugs you'll end up with. Now that might not be true, but intuitively it makes sense. If there's less things I have to manage in my head, the easier things are to understand and the harder they are to mess up.

In your capitalizeAllWords example the longer example has 5 variables to manage, while the functional version has 1 variables (the input string). A lot of the code in the imperative version doesn't pertain to what the function is doing (setting up the loop, setting up the intermediate variables, changing the variable state), while the functional version is almost completely just the operations you are interested in to complete the operation, and includes only one logical branch doing the input type check.

I'd argue that speed of execution doesn't really matter at the small scale, for Javascript. For things like rendering a Virtual DOM, it will dwarf the execution time compared to running a small user created function.

Collapse
 
anders profile image
Anders • Edited

Good points for sure. I'd argue the short version has two variables (sentence and word), but I take your meaning : ).

There is ofc more actual "variables" or pieces of data at least than that, just that they are never named, thus potentially they do not count.

The performance reflection is ofc also true, its more the general pattern of not caring that eventually gets us into trouble.

Collapse
 
moopet profile image
Ben Sinclair • Edited

I generally agree, though I think the examples you've given weren't too bad in terms of readability...

... and I'd like to point out that doing the "old fashioned" version of captilizeAllWords you've introduced a bug. Because you're not splitting on words, a double-space in the string will mess it up. That's a mistake that's harder to make using the terser format.

I do think that a lot of (especially new) Javascript coders are trying to be as terse as possible because it maybe makes them feel more advanced, or that they're doing things the "best practice" or "modern" way, but the point this post is making stands:

The style of code that you write affects how other people read and maintain it far more than it affects the application itself.

Performance is important, but premature optimisation is the cube root of all evil... and less code doesn't necessarily give more performance. Loop unrolling, for example, is a recognisable way to improve performance but it means you write more code in a messier way that future you is gonna hate.

Collapse
 
anders profile image
Anders

Quite possibly you are right there, its just "the pattern" that stands out, not necessarily that these where the worst offenders by any stretch of the imagination =)

Actually, if you do run that version of captilizeAllWords, ex: captilizeAllWords("will this fuck up?") => "Will This Fuck Up?" so it does work, unless ofc you also want it to remove any double spaces...

Your observation seems accurate to me, it seems terseness and/or cleverness is weighing too highly on the scales, especially for newer coders.

And I do as well agree that you should (mostly) not sacrifice readability for performance, but you also should not totally disregard performance, lest we all end up in the death by a thousand cuts scenario.

Collapse
 
moopet profile image
Ben Sinclair

You're right, I didn't try running it and it's obvious it'll work now I look again. But that's part of the problem, isn't it? It's not always obvious how the flow goes no matter what style you use!

Thread Thread
 
anders profile image
Anders

So true, that is our never ending challenge =)

Collapse
 
eelstork profile image
Tea

Anders, I think you need to see this:
dev.to/eelstork/introducing-howl-a...
: ))
Concision is valuable on its own. When concision works against clarity, there is a problem. How do you balance these two?
Not that simple. I'm not a huge fan of lambdas, but then again, as a programmer I predate (the widespread adoption of) lambdas. So there's an issue here of:

  • "not clear" (because we are not familiar with a notation or coding style x)
  • "not clear" (after getting enough familiarity with said coding style or notation)

You are showing "not much longer" code which ends up 3x 4x longer than the alternative. Programmers spend most time reading code and navigating code bases they know.
3x shorter, I think that's valuable when scanning through thousands of lines of code.

There isn't a standard of clarity per se (well; linters, good practices and so forth, but that is definitely not the end of it). What matters is what works for you, and your team.

Now, talking performance, the answer is really simple. BEFORE benchmarking your code there should be a perceived need for the code to run faster. Optimise for readability, concision and productivity. These concerns duly override worrying about how fast your code is running.

Collapse
 
anders profile image
Anders

∘ ㅇ IsModifier(ㄹ x) {
⤴ (howlTemplate ☰ null) ⤬
⤵ ∀ (∙ k ∈ howlTemplate) ⤴ (k ☰ x) ㆑ ⤬
}

Ohh my =)

Collapse
 
tominflux profile image
Tom

Does this count as an esoteric language?

Collapse
 
psfeng profile image
Pin-Sho Feng • Edited

You're basically comparing functional style vs imperative style. Functional style is higher level (as in C is higher level than assembly) and we often compose generic functions that can be applied in multiple situations such as reduce (used to loop through and accumulate values over collections).

It may seem less readable to the untrained eye but when you're used to it, you appreciate not having to think about the low level details, which often imply mutation, state management and so on, which with enough time will grow spaghetti and become difficult to maintain.

Collapse
 
rsa profile image
Ranieri Althoff • Edited

That is because Javascript, even though it has the concept of iterables and generators for ages, still does not use them enough.

Take Python, for instance: reversed(x) does not allocate memory if x is randomly indexable. Same for map and filter, which don't execute until you require them. That means:

const [x] = arr(x => x ** 2)

Should only execute the callback ONCE, as we are extracting just the first element. In plain JS, however, it executes for everything and returns a new array.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.