13
$\begingroup$

According to the Wikipedia article on Smalltalk, in the Expressions section,

| window |
window := Window new.
window label: 'Hello'.
window open

If a series of messages are sent to the same receiver as in the example above, they can also be written as a cascade with individual messages separated by semicolons:

Window new
  label: 'Hello';
  open

This seems like a useful feature, but I don’t know of any newer OO language that has it — not even Objective-C, which uses Smalltalk-style OO rather than Simula-style like C++.

What are some reasons to exclude this feature?

$\endgroup$

2 Answers 2

10
$\begingroup$

Depending on where you draw the line for "newer", there are some other examples. Dart has a very similar cascade notation in the form of a double dot:

// with cascade notation:
querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'))

// without cascade notation
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

As to why there aren't many other languages that do this, I can think about two reasons:

  1. This way of invocation implies the call of multiple methods that have no useful return value and instead mutate the internal state of the receiver. This is widely considered bad practice, opposed to a more functional/immutable approach.
  2. A similar result can be achieved with no additional syntax in any programming language that supports dot notation. This is commonly applied when building objects with optional or default configuration values, as in this Rust example:
let agent: Client = ClientBuilder::new()
    .user_agent(APP_USER_AGENT)
    .timeout(std::time::Duration::from_secs(8))
    .build()
    .unwrap();

All that is required is for each method to return the type of the original receiver. When incrementally configuring an object (as in the ClientBuilder example) this is a "value" type (returning a copy of the object), but it may as well be a reference that is returned for the sole purpose of allowing this "cascading" style.

$\endgroup$
2
  • $\begingroup$ Oh, I didn’t know that Dart had this! That’s interesting to see, and yes I absolutely would count this as an example of what I meant. $\endgroup$
    – Bbrk24
    May 17, 2023 at 14:48
  • $\begingroup$ It's worth noting that the convention of making methods that mutate an object return this (like the ones in your ClientBuilder example) apparently originated with Smalltalk programmers migrating to other environments and finding this feature missing in them. $\endgroup$
    – occipita
    Aug 15, 2023 at 18:00
4
$\begingroup$

One issue of this feature is implicit shadowing.

JavaScript has a with statement that works like a cascade operator that is no longer recommended and has been dropped from the current version of the standard. Commonly used implementations only support it for backwards compatibility.

It looks like this:

with (new Window) {
    label('Hello');
    open();
}

// without with:
let window = new Window;
window.label('Hello');
window.open();

MDN's page on the with statement gives some examples of the downsides that come with this feature, all due to implicit shadowing:

  • Performance: The with statement forces the specified object to be searched first for all name lookups. Therefore, all identifiers that aren't members of the specified object will be found more slowly in a with block. Moreover, the optimizer cannot make any assumptions about what each unqualified identifier refers to, so it must repeat the same property lookup every time the identifier is used.
  • Readability: The with statement makes it hard for a human reader or JavaScript compiler to decide whether an unqualified name will be found along the scope chain, and if so, in which object. [...]
  • Forward compatibility: Code using with may not be forward compatible, especially when used with something other than a plain object, which may gain more properties in the future. [...]

The last of those is especially problematic: introducing a new method to an existing class may potentially break any with statement on an instance of that class.

$\endgroup$
2
  • $\begingroup$ The performance issue you cite seems a little dubious to me. Obviously in a static environment this cost can be pushed into the compile/type checking phase, so is irrelevant, but I believe most modern Javascript virtual machines will compile a new version of each function for each set of types it is called with, so even there the cost should only be the first time a given type is used with the function. Also for languages where each statement in the block is required to be a call to a method of the given object , neither the performance nor readability issues apply. This is true ... $\endgroup$
    – occipita
    Aug 15, 2023 at 18:05
  • $\begingroup$ ... for both the Smalltalk and Rust syntaxes given as examples. The issue in javascript is that each statement in a with block may either be a call to the object or some other general statement to be interpreted as normal. This is clearly an inferior solution. The forward compatibility issue always applies to any code involving superclasses that can have new methods added, and is not, as far as I can see, made any worse by using a method chaining syntax. $\endgroup$
    – occipita
    Aug 15, 2023 at 18:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .