question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Generate nicer-looking JS for common soak operations

See original GitHub issue

In #324, I implemented a first pass at soak operations that’s correct, but uses a __guard__ (or __guardFunc__) helper function even in simple cases where it would look nicer to do something more direct. For example currentUser?.id becomes __guard__(currentUser, x => x.id) and a __guard__ function is added elsewhere in the file.

First, to establish some terminology:

  • An expression is repeatable if we can be (mostly) sure that evaluating it has no side-effects. For example, this.foo is repeatable, but this.bar() is not repeatable.
  • The soaked expression is the left side of the soak operation. So in a.b()?.c().d, it’s a.b().
  • The soak container of a soak operation is the entire expression whose evaluation will be skipped if the soaked expression is null or undefined. For example, in a(b.c()?.d.e()), the soak container is b.c()?.d.e().

Here are some rules/heuristics that I think would be a good first start:

In non-nested soak operations, if the soaked expression is repeatable and the soak container is in a statement context, wrap the statement if an if.

For example:

currentUser?.notify()

would become

if (currentUser != null) {
  currentUser.notify();
}

(This might be controversial, since I bet some people would want currentUser && currentUser.notify();, but in my opinion the if is more clear when it’s a statement.)

In non-nested soak operations, if the soaked expression is repeatable and the soak container is in an expression context, wrap in a ternary or &&

For example:

userId = currentUser?.id

would become

let userId = currentUser != null ? currentUser.id : undefined;

or maybe

let userId = currentUser && currentUser.id;

I’m still a bit undecided on whether to use &&. Might be worth a larger discussion. Of course, if we want to make it opt-in, we’d need a way of specifying preferences to decaffeinate, which doesn’t exist yet (to my knowledge).

But what if the soaked expression is not repeatable? If I understand right, the current makeRepeatable code would do a transformation like this:

userId = UserManager.currentUser()?.id

into

let value;
let userId = (value = UserManager.currentUser()) != null ? value.id : undefined;

But it would be great if we could recognize that in this case it’s safe to pull the evaluation out into its own line. We could also do a better job of picking the variable name from the expression. So Ideally we could generate code like this:

let currentUser = UserManager.currentUser();
let userId = currentUser != null ? currentUser.id : undefined.

I think it’s always safe to pull the variable into its own line as long as the soak expression is in a statement context. If the soak expression is in an expression context, we could probably do some static analysis to determine some situations where it’s safe to initialize the variable when it’s declared (we can’t do anything that would change the order or number of times that any functions are invoked, though). But maybe that could be a general improvement to makeRepeatable.

Another thought is that it’s probably still better to use a variable if the soaked expression is repeatable but really long. Maybe we could have a heuristic for that based on the number of characters in the expression.

Also, if the soak container is simple enough, the maybe function mentioned in #176 could be nice. For example, UserManager.currentUser().getFriends().first()?.id might be best written as maybe(UserManager.currentUser().getFriends().first()).id.

Anyway, that’s a lot of different ideas, but I wanted to keep the discussion from #176 going. It may make sense to pull some of these into subtasks. Maybe a good scope of this issue is that 80-90% of soak operations in a typical codebase should convert to JS code that doesn’t use __guard__. I think that’s reasonable, although it’s hard to say for sure.

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:3
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
Retsamcommented, May 14, 2018

Not sure if it made more sense to put this in #1103 or here, but lodash has a lot of methods beyond _.get which can be very helpful for cleaning stuff up:

  • _.invoke does function calls on soaked expressions: a?.b.c(d) is equivalent to _.invoke(a, "b.c", d).

  • _.set does assignment on soaked expressions: a?.b = c is equivalent to _.set(a, "b", c)

  • And _.update can do assignment based on the previous value, so a?.b += 3 is equivalent to _.update(a, "b", b => b + 3)

  • (Opinionated, but _.isNil(x) is an alternative to x == null for replacing x?, that I personally prefer, as I think == null is a bit of a gotcha for less experienced JS developers and requires a specific linter exception)

I believe any soak container can be expressed as a combination of these functions, it works in statement or expression context, and the end result is reasonably concise and readable and should produce identical results to the coffeescript. For example a.b()?.c().d += 5 becomes:

_.update(_.get(_.invoke(a.b(), "c",), "d"), x => x + 5)
1reaction
alangpiercecommented, Jul 22, 2016

For example, this isn’t: userId = UserManager.currentUser()?.id

Right, if we wanted to handle that exact case, we could maybe compute a predicate like isBeforeAnySideEffectsWithinStatement or something. I think it should be possible to implement a defensive version of that by reusing isRepeatable (although an isPure would be more precise) and using the evaluation order. For example, the assignment expression case might be something like this:

if (this.isBeforeSideEffects) {
  this.assignee.setIsBeforeSideEffects();
  if (this.assignee.isRepeatable()) {
    this.expression.setIsBeforeSideEffects();
  }
}

We could probably consolidate that logic by having each node expose the evaluation order of its child patchers or something.

But I also really like your idea of extracting multiple variables in a row, which would automatically handle the isBeforeSideEffects use case and more. It would be great if makeRepeatable always (or almost always) did that kind of thing.

One thought: maybe this logic could be moved to a post-processing step on JS code? I’m a bit scared of adding too much complexity to MainStage, especially since repeated magic-string operations can get really fragile. One idea is to leave makeRepeatable as-is (an inline assignment to a fresh variable), but choose a dummy name like __tmp1, then pick a name for it once it’s in JS (which I think makes it a lot easier to use expression-namer?), and also move assignment expressions into their own statements as much as possible as a JS-to-JS transform.

But you’re right that handling the easier cases is a good first step.

Read more comments on GitHub >

github_iconTop Results From Across the Web

What is Soak Testing? Types of Software Performance Testing
Soak testing detects performance-related issues by requesting the designed load on a system. Learn more about software performance testing types.
Read more >
What is Soak Testing? Definition, Meaning, Examples - Guru99
Soak Testing is a type of non functional testing that is used to measure performance of a software application under a huge volume...
Read more >
What is soak testing? | Definition from TechTarget
A quality assurance (QA) professional incorporates soak testing before an application or an app update reaches production, where it is available to real...
Read more >
What is Soak Testing? How to create a Soak Test in k6
A soak test uncovers performance and reliability issues stemming from a system being under pressure for an extended period.
Read more >
Understanding Setpoint Ramping and Ramp/Soak ...
Soak Segment (also called Dwell Segment) maintains the value of the previous segment for a defined time. Hold Segment maintains the value of...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found