Proposal: Command Syntax

I am new to the proposal process but have 2 related ideas (second idea here) I would like to present for consideration. This one was born out of my strong affinity for the goodness of the Command-Query Separation principle.

Proposal: Command Syntax for JavaScript

This is the first group to which I am presenting the idea. I figured it was a good place to start. Your feedback is welcome.

Thank you for your thoughtful consideration.

I agree it can be valuable to know which functions are "commands" and which are "queries".

I don't necessarily agree that it should be considered a bad practice for command functions to return values (yes, I know I'm going against the common teaching here). The reason why it's "bad" for commands to return values is because it indicates that the command is doing multiple things at once, and it would be better to divide the function in two, having the caller first use the command function then the query function. At best, we can think of this as a slight code smell, indicating that something may (or may not) be off in the code we're looking at. I feel it's unwise to elevate this to anything more than that (and try to actively discourage it in any way). There's two reasons for this:

  1. It has tons of false positives. There are many, many good examples of times where command functions need to return information (i.e. for performance reasons, or race-condition reasons, or something else). Trying to enforce a command-should-not-return rule would just cause way too much friction, and maybe even create problems of its own (i.e. people are actively creating race conditions more often than normal, because they're trying to separate queries from commands).
  2. If a command function is returning something when it really doesn't need to, then we haven't found the real issue yet, only a symptom of it. The real issue was just that the function was poorly designed and is doing too much. There's probably a lot of other problems going on as well, that's not going to be fixed via nudges from the language like this.

And yes, I saw that you provided a !. syntax to allow people to explicitly state that "this is a command, but I want to use its return value", but the fact that the "!" exists by itself is, IMO, pushing a behavior that I feel is unwise to be pushed.

Still, the code idea of finding some way to separate commands from queries is kind-of nice. Not sure if it's something that JavaScript itself would adopt, but a typed superset could potentially adopt something like this (allowing you to mark certain functions as queries or commands with the type syntax).

1 Like

In my opinion once you've experienced functional programming and have an affinity for thinking about how to divide a program into pure and impure modules, anything which strikingly calls out the distinction between pure (queries) and impure (commands) is welcome.

It tells you this is the frightening code where extra care must be taken and where bugs are more likely to be found, and this is the safe code where I'm going to have a much easier time reasoning about and solving issues.

As someone who did OOP for a couple decades before getting into FP, I can't tell you how great it felt to find a better way to manage programmatic complexity. It made programming feel simple again.

I'll make another point. Once you have an async function everything which follows must by definition also be async. Read this for a clear explanation. Well, precisely the same is true of commands. If you have a command and you compose additional queries onto it (after mutating the subject argument) it remains a command, because of the initial mutation.

The point is commands are of the more serious variety of function because they are the doers. So if you have a function which is a command and a query it is really a command.

Thus, calling attention to that function is still deserved. The more difficult part of every program is always where the commands reside. If you segregate your program into pure (query only) and impure (side effecting) parts, your trouble will nearly always reside in the latter. It's not that you can't have bugs in pure code. It's just that they're so ridiculously easy to fix by comparison, because time/timing is not a factor.

Thank you for your feedback.

This is just a small part of it. The bigger part is whether the command is doing 2 things is mostly irrelevant. If it has side effects it's a command. The syntax just calls it out as such.

And I don't really care how people write their commands. I'm generally going to try to write return-nothing commands (!), but I will sometimes write return-something commands (!.). This syntax isn't so much meant to dissuade that as it is to simply flag commands—of both flavors.

And also, to permit method chaining (fluent interface) while allowing you to also write return-nothing commands.

1 Like

I would like some clarification on this point:

So, is a function like this a command or a query?

function getUserInfo(userId) {
  return fetch(`https://${HOST}/users/${encodeURIComponent(userId)}`)
    .then(res => res.json());
}

It's not changing the state of the system, rather, it's just querying an external resource. But, it does perform a side-effect (it sends out an HTTP request) in order to perform that query, and so it is not a pure function.

Reading various online resources, it feels like a function like this is supposed to be interpreted as a query (not a command), but those same online resources like to use the terminology "no side-effects" or "impure" to describe queries, which if we took those statements literally, it would mean that the above should be interpreted as a command. I suspect they were just using the terms "side-effect" and "pure" more loosely than how it's traditionally defined. But, I want to check to see how you're defining a command and query, and if you really mean command==impure and query==pure.

If so, there is at least one other thread I know of, that discussed differentiating between pure and impure functions, along with some runtime enforcement of it, that may be applicable to this discussion: Add pure function modifier, build Hoogle equivalent

I am reminded of bang methods in Ruby. Method names are allowed to end with ? and !, with the convention being that the former indicated a safe query, and the latter indicating there is a mutation.

A challenge in adding a convention like this retrospectively means that existing code doesn't follow the convention and there is no guarantee on how much new code will adopt it. So readers may not be able to rely on the convention to aid rapid understanding in the same way that they can in other languages.

In Rails/ActiveRecord the convention is different, as i recall - more that bang methods will raise if they fail.

1 Like

A challenge in adding a convention like this retrospectively means that existing code doesn't follow the convention and there is no guarantee on how much new code will adopt it.

This is a syntax which works retroactive with all past JavaScript, since it extends the spec in a nonbreaking way. So it's entirely opt in, yes. And people who don't want it can opt out.

But even TypeScript whose slogan is "JavaScript with syntax for types" is an extension to the language which some people choose to use and some people not. And saying all JavaScript should be TypeScript, obviously, makes no sense even though it has, in effect, become its own standalone proposal anyway.

The point I'm making is that just because some people prefer compile time type safety doesn't mean all people do. Thus, calling out types in JS will always be opt-in, for those who prefer that approach.

I have a greater affinity for CQS than static types. And so I would opt into that practice, if it provided its own syntax. Command Syntax is like TypeScript but in another realm and for those, probably FP-minded programmers, who appreciate why separating commands from queries is a good thing. But saying all JS must be this or that is not an argument I'm making. People are people and they can choose their own priorities. Not all JS will get type annotations even if that proposal makes the cut.

There will never be a universally acceptable way of doing JS, just options, preferences, and styles. And things you think are important today, as a dev, may change in the years to come. Ten years ago, before discovering Rich Hickey's talks, and his flavor of FP, I had none of these concerns.

If your concern is adopters, you're right. Programming is a craft. And not everyone will use all the things, but that doesn't mean the tools can't be made available.

The trouble is that JS was initially given the veneer of an OOP language. And FP is experiencing a resurgence and is trickling into the language (pipeline operators, partial application syntax, records and tuples) but has not yet gained the broader JS community. I feel that JS devs who do FP in JS are more likely to appreciate these proposals than those who have only dabbled in it or still think primarily in the object-oriented paradigm. I like to keep a foot in both camps (FOOP) but these are indeed FP-oriented proposals.

GetUserInfo adds a deliberate point of confusion. It calls/queries via fetch. But this instance of calling/querying should not be confused with the "query" of CQS. They are related but different. This is unequivocally a command. It is a command because it uses an external endpoint whose results, given the same arguments, may vary based on when it's called.

That function would be invoked getUserInfo!. using command syntax:

  • queries — always pure, never mutating
  • return-nothing commands (!) — impure (side effecting) with no return value
  • return-something commands (!.) — impure (side effecting/ed) with a return value

Yours is that last flavor. As I read CQS on Wikipedia I realize even the writers there have a note of confusion.

[CQS] states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer.[1] More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

By their own definition commands "perform actions" and queries "return data", as you inferred. But then they get into "referential transparency" which is founded on the purity aspect. So the terms command and query as used appear to be more synonymous with pure and impure than putting and taking once you arrive at their own conclusion.

The interesting aspect of your sample function is how it shows commands must sometimes return a value. Therefore, it is clearer to say that a command can be side effecting (causing) or side effected (impacted by side effects caused elsewhere), as with your function. Still, it reinforces all three use cases are valid, at times.

The syntax is optional and people can use or not use it, just as with type annotations. Although the syntax adds a useful semantic that goes beyond just syntax (e.g. fluent interfaces for commands), it's for those who want to distinguish between pure and impure, safe and unsafe—especially since doing so makes code easier to understand where the actual side effects come into play.