top | item 36002105

'Do' More with 'Run'

43 points| mgreenw | 2 years ago |maxgreenwald.me

30 comments

order

Waterluvian|2 years ago

Feels like code golf. The two run examples are basically the same, but now I have to reason about what ‘run’ is and I’ve made my stack trace more complicated.

I am in love with “everything is an expression” from my time with Rust. I regularly use a ‘let’ and wish I could just have the entire conditional feed into a ‘const’ given it’s never going to change after the block of code responsible for assignment.

I wish there were more generations of ‘use strict’ so that we could make bolder, more breaking changes to the language without breaking stuff or dying in a horrible fire of “just roll your own dialect using compiler plugins.”

Gordonjcp|2 years ago

> I regularly use a ‘let’ and wish I could just have the entire conditional feed into a ‘const’ given it’s never going to change after the block of code responsible for assignment.

I'm currently doing a lot of audio work, where I kind of want to define some parameters early on that won't change, except if something "magic" happens. So, a kind of "unlockable" constant. Think in terms of, a bunch of filter coefficients that are predetermined by the design of the filter but need to be calculated to match a given sample rate.

Just a kind of "set and forget" variable, that ought not be written to more than once, or maybe only written to by the thing that first wrote it.

mgreenw|2 years ago

> I am in love with “everything is an expression” from my time with Rust

Totally agreed! I wish JS had if expressions (maybe in the future?). It doesn't seem like such a huge change if it were rolled out slowly like other new syntax features but maybe I have no idea what I'm talking about.

Hopefully things like `run` can help move the needle on this. I like it because it feels more FP and intentional than IIFE's everywhere.

aidenn0|2 years ago

Not even code golf given that the example is a single character longer. Naming it "r" would be code golf (and "r" is definitely worse than "run" for reasoning).

otterley|2 years ago

What some people call "code golf," others call "syntactic sugar." :-)

gtsop|2 years ago

I appreciate the sentiment, but i do not agree with the solution.

The actual solution is to extract a function. What is the legitimate excuse for not extracting a function (other than being lazy, which i will not accept as an argument)

Edit: Just to enhance my comment, having a separate function with a distinct name enhances readability and maintainability. Readability is enhanced because a clear name lets the reader understand what is happening in the function (this attribute is lost with self calling nameless functions). Also, less lines need to be read to understand the wider context. Maintainability is enhanced because 1) more readable code is easier to reason about and change and 2) extracting a function means it can be reused

mgreenw|2 years ago

Try using `run` in the return of a React (or similar) component and you'll never go back ;)

There is always a balance here. I'm not saying to never extract a named function, and there is certainly good reason to do that, especially if the function is called elsewhere or is quite complex.

But, in many cases, the inline logic is more readable because it's right there, and the function really doesn't need a name.

chatmasta|2 years ago

Extracting to a function means you need to give it a name. There is a middleground where you might want a function for control-flow purposes, but giving it a name makes the code look more complicated than it needs to be. Personally I just use an IIFE in those situations - and I don't see much benefit from run() as compared to an IIFE.

tln|2 years ago

I like it! Another one-liner I'm constantly adding is

    const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
Whatever runs the main function should probably do more, like handling rejections.

SirensOfTitan|2 years ago

One control flow function I use often in JavaScript is my `runIfUnhandled` function:

    export const UNHANDLED: unique symbol = Symbol("UNHANDLED");
    export function runIfUnhandled<TReturn = void>(
      ifUnhandled: () => TReturn,
      run: (unhandled: Unhandled) => TReturn | Unhandled,
    ): TReturn {
      const runResult = run(UNHANDLED);
      if (runResult === UNHANDLED) {
        return ifUnhandled();
      }
      return runResult;
    }
I often use it to use guards to bail early, while keeping the default path dry. In particular, I used it heavily in a custom editor I built for a prior project, example:

        runIfUnhandled(
          () => serialize(node, format),
          (UNHANDLED) => {
            if (
              !Element.isElement(node) ||
              !queries.isBlockquoteElement(node)
            ) {
              return UNHANDLED;
            }

            switch (format) {
              case EditorDocumentFormat.Markdown: {
                const serialized = serialize(node, format) as string;
                return `> ${serialized}`;
              }
              default: {
                return UNHANDLED;
              }
            }
          },
        );

brundolf|2 years ago

Another short utility I often add (that I wish were a language feature):

  function given<T, R>(
    val: T|null|undefined,
    fn: (val: T) => R
  ): R|null|undefined {
    if (val != null) {
      return fn(val)
    } else {
      return val // null|undefined
    }
  }

  function greet(name: string|null) {
    return given(name, name =>  'Hello ' + name)
  }
This is equivalent to eg. Rust's .map()

Can also do a version without the null check for just declaring intermediate values:

  function having<T, R>(
    val: T,
    fn: (val: T) => R
  ): R {
    return fn(val)
  }

  const x = 
    having(2 * 2, prod =>
      prod + 1)

haburka|2 years ago

If I ever see this shit in code review I’m flagging it. IIFEs exist to avoid polluting global space in JS - its not something you are often dealing with in modern development. Especially the react example - make a new component, this prevents better readability like a new component or using an inline function.

Additionally for ever developer that hasn’t seen this, they have to look up the run function and try to grasp WTF it is doing. And god forbid someone modifies the run function.

jlhawn|2 years ago

In the "Use as a `do` expression" section, the example which uses `run` does not need the `else` cases and could be simplified:

  function doWork() {
    const x = run(() => {
      if (foo()) return f();
      if (bar()) return g();
      return h();
    });
  
    return x * 10;
  }

bestcoder69|2 years ago

Can also get rid of the `run` and move parens to simplify even further:

  function doWork() {
    const x = () => {
      if (foo()) return f();
      if (bar()) return g();
      return h();
    };
  
    return x() * 10;
  }

mgreenw|2 years ago

Completely agreed, and I use this if-based early return syntax frequently! That being said, I like using `if` and `else if` and `else`, but maybe that's just me! I don't think there's a substantial difference in readability or utility.

aidenn0|2 years ago

Parentheses phobia strikes again. This is 1 character longer than an IIFE (since you replace "()" with "run"

asherah|2 years ago

i'd say that it's a bit more readable:

  run(() => { return foo })
looks better than, and assuming pre-existing knowledge of what `run` does, is more understandable than

  (() => { return foo })()
but this is also a fairly contrived example

mgreenw|2 years ago

Yes, I do think the extra parens are less readable.

Its not about number of characters, its about reasoning that the inline function that you just wrapped in parens is then called later, potentially after many lines. At least with `run` it's immediately clear based on the name that you are running the function.

Edit: This is pretty funny. I'm a parensaphobe! (not really) https://www.reddit.com/r/ProgrammerHumor/comments/qawpws/the...