Proposal: deprecate ternary

See right above =P

Hummm I think I’d rather preserve ternary for this than deprecating ternary and adding that syntax.

2 Likes

How about scala syntax?

if (i == 1) x else y

This has the advantage of being clearly different to regular if blocks, where the brackets are removed.

Since we’re talking about ternary style code, each if is guaranteed to have an else. Perhaps that can be a condition of removing the braces? The above would then format to if display { if present log() else warn() }.

2 Likes

What happens to if (i == 1) if (j == 2) x else y? Assuming x and y can be unit

1 Like

This else branch belongs to second if in Scala. but tbh this leads to ambigiuos/complex code.

I prefer not having ternary removed. It makes sense to have them, they are sometimes more concise than if expressions and people coming from js will find a direct translation.

Maybe we can do something about complex ternaries.

  • Warn/discourage complex ternaries in documentation or in a separate official style guide.
  • Print complex ternaries (long/nested) into if expressions so when people use formatter printer will purge complex ternaries away.
3 Likes

Assuming my earlier statement about “ternary style code requires an else”, I would parse it assuming the innermost if is the ternary.

if i == 1 {
  if (j == 2) x else y
}

This does bring us back to whether nested ternaries should be allowed, though, because a valid nested ternary in this style would be if (i == 1) if (j == 2) x else y else z. Which is about as difficult to follow as i == 1 ? i == 2 ? x : y : z without multi line formatting.

2 Likes

Personally I’d rather we remove the if statement than the ternary operator. Especially if we’re trying to guide people to use switch properly to handle all use-cases.

This is a big reason why I’d want a ternary over an if statement. A ternary is a mini-switch for a boolean value, forcing the developer to handle all possible cases. Encouraging if statements to be used more often can result in more scenarios where possible states for an application go unhandled (preventing that is a big reason for why I chose ReScript).

Here’s some real-life ternary usage from the project I’m currently working on.

      <Framer.Div
        className={"fixed top-0 bottom-0 left-0 right-0 z-2000 text-base" ++ (
          visible ? "" : " no-interaction"
        )}
        initial={"background": "rgba(0,0,0,0)"}
        animate={"background": visible ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0)"}
        onClick=closeHandler
        ref={ReactDOM.Ref.domRef(refBackground)}
      />

In the above I wouldn’t want to lose the ternary operator.

That same component a few lines later does the following, to which I’ll agree that it can probably be refactored to a switch case, though I’d say it’s up for debate on whether that’s easier to parse.

        {visible
          ? <OpenSocial.Components.Molecules.Tray className={"text-base max-w-lg z-2000 bg-white flex flex-column"}>
              <ChatWindow closeChat={_ => setVisible(_ => false)} />
            </OpenSocial.Components.Molecules.Tray>
          : React.null}

This is what I’d reformat that as with a switch

        {switch (visible) {
          | true => <OpenSocial.Components.Molecules.Tray className={"text-base max-w-lg z-2000 bg-white flex flex-column"}>
                      <ChatWindow closeChat={_ => setVisible(_ => false)} />
                    </OpenSocial.Components.Molecules.Tray>
          | false => React.null
        }}

With switch statements and JSX components I always struggle with the indentation, since I want to have my opening and closing tags on the same indentation level for easiest readability.

The alternative indentation would be the following which I dislike for disconnecting the true condition from the component. Although I suppose I could get used to it.

        {switch (visible) {
          | true =>
              <OpenSocial.Components.Molecules.Tray className={"text-base max-w-lg z-2000 bg-white flex flex-column"}>
                <ChatWindow closeChat={_ => setVisible(_ => false)} />
              </OpenSocial.Components.Molecules.Tray>
          | false => React.null
        }}

To round off two other examples of a ternary usage in the code-base.

let className = "overflow-auto flex" ++ (reverse ? " flex-column-reverse " : " ") ++ className->Belt.Option.getWithDefault("");
let getLastReadDisplay = (conversation) => {
  open Belt.Option;
  conversation
    ->getLastRead
    ->flatMap(lastRead => conversation->hasMessagesSince(lastRead) ? Some(lastRead) : None)
}

All-in all, a ternary operator is just a really useful way to do a bool -> x type transformation.

1 Like

I do have that kind of ternaries in my app :smile:

let count =
  1 + (redactedA > 0 ? 1 : 0) + (redactedB > 0 ? 1 : 0)
<CallToActionLink
  href={link.href ++ (queryString === "" ? "" : "?" ++ queryString)}
  title={link.title}
/>
1 Like

using keywords instead of braces makes it a bit more readable on a single line. And it works at multiline too. Downside is that this introduces another keyword (I think might not be necessary with a handwritten parser). Another downside is that it’s definitely less JavaScript like.

let a = if bla == 0 then 0 else 1 end
let a = if bla == 0 then
  aLongerExample()
else 
  aLongerExample2()
end

It does look less bad when the condition isn’t (and shouldn’t be) a single letter:

let count =
  1 + (if redactedA > 0 {1} : {0}) + (if redactedB > 0 {1} : {0})
<CallToActionLink
  href={link.href ++ (if queryString === "" {""} {"?" ++ queryString})}
  title={link.title}
/>

I feel that’s not too bad?

Definitely helps here… though we’re not gonna go back to that form. A different can of worm for other usage cases.

1 Like

Sure it’s not too bad, but I find it a bit harder to “humanly” parse on single line contexts like this.

1 Like

I agree here, I never use if in my code, switch is both more readable and more powerful to the point if is almost a code smell to me.

So I’d rather remove if and keep the ternary operator that I use for brevity.

1 Like

Eeeh we can keep the discussion going but we’re not removing if in this language… If we’re debating over familiarity then I don’t see how removing if helps at all.

1 Like

I’m not saying we should remove if. I’m just trying to make my case for why the ternary operator should stay (and should not be replaced by if).

On a scale of usefulness the order for me would be

  1. switch
  2. ternary
  3. if

In my relatively small ReScript project I have: 71 switch statements, 13 usages of the ternary operator, 8 if statements

There’s 3 ternary operators I could rewrite as switch with minimal improvement. There’s one where I would consider using an if with no else.

let onScrollEnd = React.useCallback3(
      () => hasPreviousPage ? setBefore(_ => startCursor) : (),
      (setBefore, startCursor, hasPreviousPage)
    );

Although I’m so used of reading the variable name with a question mark and seeing what happens that it’d probably slow me down more to have if (hasPreviousPage) { setBefore(..) } than it’d help me.

In the other cases I don’t think replacing the ternary would be a positive improvement.

Of the 8 if-statements, 1 should’ve been a switch-statement, 1 should’ve been incorporated into the arm of the switch it’s in. That leaves me with 6 that are in the form where the alternatives are worse, mostly in the form of.

some block of code doing stuff

if (someCondition) {
  perform extra work
}

continue
3 Likes

I wouldn’t mind deprecating ternary. I know for a fact some team mates definitely think otherwise… and I’m still arguing against point free etc every now and then.

Readability is far more important than brevity to me. Although ternary is commonly known i find it easier to read if .. else ... I use switch 99% of the time.
Having a formatter leading away from ternary would be a nice transition in my opinion.

3 Likes

And what would you replace it with? someCondition ? extraWork() : ()?

I wouldn’t replace it. I am arguing for keeping ternary, not arguing for removing if.

2 Likes

Popping back in here… For me, one-line with if/else syntax is a lot harder to parse at a glance than ternary, even with all the syntax/format adjustments proposed. (The only one-line if/else I’ve ever seen and liked is clojure but lisp syntax is not helpful here)

Since it’s more of a stylistic concern, is it too complex to have it be configurable whether the compiler allows ternary? If @chenglou commented on configurability, I missed it.

Given the opportunity, I would happily welcome a hard error on nested ternaries, but still would prefer the option to use shallow ternaries.

1 Like

+1 for keeping ternary.

Reasoning:

[1] If the ReScript philosophy is to provide a JS like syntax that is familiar for JS developers, then keeping the syntax seems consistent with that philosophy (and especially considering its widespread usage).

[2] When evangelising ReScript to JS teams, we will often face resistance to change and each small difference is one more barrier to overcome. So each factor that is the same as JS then it helps to combat “death by a thousand cuts”.

Thanks.

6 Likes

Understood

No we won’t be exposing any printer configuration; that’d be one step forward ten steps back. Lots of funny problems when there’s a config (hierarchical, monorepo, fs, editor, etc.).

For the printer, we try not to have heuristics that are overly opinionated. Hard erroring on an otherwise ok pattern would be such heuristics. We draw the line around nudging you by printing to another format, when we have to.

2 Likes

Makes sense. I haven’t messed much with bsconfig to see how it differs/compares to tsconfig, which allows some settings that are arguably stylistic