DEV Community

Discussion on: Why do we write JavaScript like this?

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.