Skip to content

Instantly share code, notes, and snippets.

@dherman
Last active June 30, 2021 14:21
Show Gist options
  • Star 67 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dherman/1c97dfb25179fa34a41b5fff040f9879 to your computer and use it in GitHub Desktop.
Save dherman/1c97dfb25179fa34a41b5fff040f9879 to your computer and use it in GitHub Desktop.

Motivation

  • expression-oriented programming one of the great advances of FP
  • expressions plug together like legos, making more malleable programming experience in-the-small

Examples

Write in an expression-oriented style, scoping variables as locally as possible:

let x = do {
  let tmp = f();
  tmp * tmp + 1
};

Use conditional statements as expressions, instead of awkward nested ternaries:

let x = do {
  if (foo()) { f() }
  else if (bar()) { g() }
  else { h() }
};

Especially nice for templating languages like JSX:

return (
  <nav>
    <Home />
    {
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />
        }
      }
    }
  </nav>
)

Tennant's Correspondence Principle

  • key refactoring principles:
    • do { <expr>; } equivalent to <expr>
    • (do { <stmt> };) equivalent to { <stmt> }
  • this semantic transparency is demonstrated by the semantics:
    1. Return the result of evaluating Body.

Further considerations

How to avoid either parsing conflict in statement context with do-while, or dangling-else type of ambiguity:

do do f(); while (x);

I have several alternatives I intend to explore here.

@iddan
Copy link

iddan commented Apr 13, 2017

So maybe we can vote on "do return"?

@erights
Copy link

erights commented Apr 22, 2017

Please transfer proposal to tc39

See tc39/proposals#44

@trustedtomato
Copy link

If we are in an async function, would await work in a do expression?

(async function(){
    const foo = do{
        await anAsyncFunc()
   }
   // Error or the foo is the awaited anAsyncFunc()?
})();

@ivan-kleshnin
Copy link

ivan-kleshnin commented Jul 5, 2017

Ok, guys, should let x = do {do { true }} resolve to true and if not – why?
Current Babel does not parse it and other authors aren't sure as well.

@rubencodes
Copy link

Is there any argument for why it shouldn't? Seems intuitive to me that it would...

@Jamesernator
Copy link

Jamesernator commented Jul 8, 2017

@ivan-kleshnin This happens because do-expressions must strictly be in expression position, it's the same reason why you can't write an immediately invoked function expression without wrapping it brackets:

function() { console.log("Hello") }() // Does not work
(function() { console.log("Hello")()) // Does work

let x = do { do { true } } // Does not work
let x = do { (do { true } }) } // Does work

Of course there's no spec text yet so it could still happen, Babel's parser (and v8's experimental implementation) are thus just guesses as to what the exact grammar might be, if there's no async variant I don't really see the purpose of having it in statement position when you could just use a block:

do {
    const greeting = "Hello!"
    console.log(greeting)
}

// vs just

{
    const greeting = "Hello!"
    console.log(greeting)
}

Although if there's an async variant I think it'd be rather nice for top-level execution e.g.:

module.exports = function myCoolFunction() {

}

if (require.main === module) {
    do async {
        // Execute my cool cli tool
    }
}

@ivan-kleshnin
Copy link

@Jamesernator thank you for the explanation – you gave a lot of good points.

I don't really see the purpose of having it in statement position when you could just use a block:

The original question was caused by the use of do in JSX.

@ecbrodie
Copy link

@dherman or anyone: I have a question about whether it is valid to use the do expression for an if statement WITHOUT AN else CONDITION. When I use a do expression without else, I get an eslint error for no-unused-expressions. I am using this code in my JSX for a React component, something like this (illustrative purposes only):

export default function myComponent(props) {
  return (
    <div>
      {do {
        if (true) {
          <p>If statement is true</p>
        }
      }}
      <p>I am always rendered</p>
    </div>
  );
}

I prefer to use the do construct over the unnatural, confusing usage of &&, as suggested by Facebook for React rendering. Is this a valid use case?

Note that babel/eslint-plugin-babel#13 may be related to this issue, but I don't know for sure yet until I understand more about the design of the do expression.

Thank you.

@ecbrodie
Copy link

@Jamesernator
Copy link

@ecbrodie There's no reason that wouldn't work, the completion value of if is already well defined, basically you see the result of anything a do-block could do just by eval-ing the body e.g.:

const x = eval(`
    if (true) {
        'banana'
    }
}
x // 'banana'

const y = eval(`
    if (false) {
        'banana'
    }
}

y // undefined

@streamich
Copy link

streamich commented Sep 9, 2017

Regarding this JSX example:

return (
  <nav>
    <Home />
    {
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />
        }
      }
    }
  </nav>
)

I got triggered so badly on this:

Especially nice for templating languages like JSX...

First, the ternary expression is million times better in this situation:

return (
  <nav>
    <Home />
    {loggedIn ? <LogoutButton /> : <LoginButton />}
  </nav>
)

Second, no need to use JSX, just use the HyperScript function h, which makes it another million times better:

return h('nav',
  h(Home),
  loggedIn ? h(LogoutButton) : h(LoginButton),
);

Or JSON-ML (hint: JSON is built into JavaScript by default; no need to use the defunct XML syntax in JavaScript):

return ['nav',
  [Home],
  loggedIn ? [LogoutButton] : [LoginButton],
];

Reported for this example.

just kidding ;)

@drhumlen
Copy link

Is there any activity on this? Or similar proposals elsewhere I could look at or try out?

I need this really badly -- especially when working with JSX.

@streamich A ternary is pretty nice when there's only 2 branches (true or false), but if there's 3-4-5 it quickly becomes completely unreadable and you're forced to make a helper function with return to avoid using a temporary variable.

One could argue that splitting things into functions is "right" anyway, but I want refactoring into functions/consts a conscious decisions, not something I have to do because of language restrictions. Inline is often easier to read than having to jump back and forth between intermediary helper functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment