Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring sqPath #2986

Merged
merged 22 commits into from
Jan 23, 2024
Merged

Refactoring sqPath #2986

merged 22 commits into from
Jan 23, 2024

Conversation

OAGr
Copy link
Contributor

@OAGr OAGr commented Jan 19, 2024

I think this is decent, minus the test.

Copy link

changeset-bot bot commented Jan 19, 2024

⚠️ No Changeset found

Latest commit: 76a9f2a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Jan 19, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
quri-hub ✅ Ready (Inspect) Visit Preview Jan 23, 2024 10:39pm
squiggle-website ✅ Ready (Inspect) Visit Preview Jan 23, 2024 10:39pm
2 Ignored Deployments
Name Status Preview Updated (UTC)
quri-ui ⬜️ Ignored (Inspect) Visit Preview Jan 23, 2024 10:39pm
squiggle-components ⬜️ Ignored (Inspect) Visit Preview Jan 23, 2024 10:39pm

* main:
  VoidWidget for empty result values
  cleanup some types
  empty programs with possible comments are valid
  minor cleanup
  cleanups; remove some utility functions
  extract ViewerBody and ViewerMenu components
return valuePathEdgeIsEqual(this.value, other.value);
}

toDisplayString(): string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS objects always have toString(), so someone could call it by accident and get [object Object].

I recommend renaming either toDisplayString() or uid() to toString().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

How about I make a new toString() method that calls one of them (probably "toDisplayString". That way users could call toDisplayString, which I think is more precise.

}: {
ast: ASTNode;
offset: number;
}): SqValuePath | undefined {
return new SqValuePath({
root: "bindings", // not important, will probably be removed soon
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on this old comment:

  • I don't believe anymore that we'll remove the root info here, it's important
  • viewValuePath in ViewerProvider is currently broken when "Result" or "Imports" tab is selected, or even "Exports"; we should switch the dropdown to "Variables" when "Find in viewer" hotkey is pressed, and be more clever about "Exports" (maybe you already noticed this when you added gutter circles)
  • we could implement "find in editor" on imports, but we'd have to switch the viewer to "Imports" tab, and I'm not sure if it's worth it since we'll also get tooltips soon

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... makes sense. I'd like to fix this if it's not too much work - for keyboard navigation and stuff to work well. Will see though, later. lots to do.

});
}

itemsAsValuePaths({ includeRoot = false }) {
uid(): string {
return `${this.root}--${this.edges.map((edge) => edge.uid()).join("--")}`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe / for separators instead of --?

Also, it's still possible to create non-unique uids with this code:
Screenshot 2024-01-20 at 15 35 38

So you need to escape () in SqValuePathEdge.uid().

Copy link
Contributor Author

@OAGr OAGr Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to use /.

Good catch. I wasn't trying too hard to force non-unique IDs here, but happy to improve it like so.

}

// Checks if this SqValuePath completely contains all of the nodes in this other one.
contains(smallerItem: SqValuePath) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startsWith(prefix: SqValuePath)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about hasAsPrefix or hasPrefix

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works too. I suggested startsWith because of String.startsWith.


export function pathIsEqual(path1: SqValuePath, path2: SqValuePath) {
return pathAsString(path1) === pathAsString(path2);
export function pathToDisplayString(path: SqValuePath) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be path.toDisplayString()
(or path.toString(), see my other comment)

Copy link
Contributor Author

@OAGr OAGr Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The annoying thing here is that in utils, we use different names for the root elements than in squiggle-lang. I could do a regex to replace, but that seemed hacky.

Maybe we could rename them in squiggle-lang to match Squiggle-viewer? "Bindings" -> "Variables", capitalize all of them?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Yeah, I missed that this depends on topLevelName.

The problem with renaming is that roots also match keys in SqOutput dicts, and it's nice even if we don't rely on it anywhere.

In export type ValuePathRoot = "result" | "bindings" | "imports" | "exports", I thought about suggesting export type ValuePathRoot = keyof SqOutput, but they might diverge at some point (output could include other, non-value things), so I'm not sure if that's a good idea.

Using public names in SqOutput is too much (and problematic re: versioned-components), so... I dunno. Maybe just leave it here for now.

});

for (const subValuePath of subValuePaths) {
const pathEdge = subValuePath.lastItem()!; // We know it's not empty, because includeRoot is false.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some time ago I converted this function to traverse the path items manually, instead of pre-generating all prefixes, because of a habit to fight all O(N^2) that I encounter; now you converted it back...

I guess because you disliked C-style for(;;) loop and the way I constructed calculatorPath? Oh well.

The real wart on this function, though, is that it depends on itemStore. Otherwise it would belong to SqValue as a method.

Maybe we can still move it there?

class SqAbstractValue {
  ...
  getSubvalueByPath(
    path: SqValuePath,
    traverseCalculatorEdge: (path: SqValuePath) => SqValueResult
  ) {
    ...
  }

You could even try to split the implementation into specific SqValue classes by implementing another function, getPathEdge(edge: SqValuePathEdge) (but I'm not sure how to handle calculators with this)

@@ -68,7 +74,7 @@ export function wrapValue(value: Value, context?: SqValueContext) {
}
}

export abstract class SqAbstractValue<Type extends string, JSType> {
export abstract class SqAbstractValue<Type extends string, JSType, ValueType> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extra type seemed safe to have. I think I needed it for getSubvalueByPath at some point - not sure if I need it now, but it seems good to have anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use it only for value(), I don't think it's useful.

If you had a value that's proven to be a specific SqValue (e.g. SqNumberValue), you could extract this:

Screenshot 2024-01-23 at 15 20 00

OTOH, if you add a type parameter like this, then you have to provide it in every subclass, when previously TypeScript would happily infer it for you.

(There's still no partial type argument inference in TypeScript)


There's a longer sub-discussion here about when we should explicitly provide types that could be inferred.

I've noticed that you prefer to write out types more often than I do.

I generally like to rely on type inference, except for cases when I want to guarantee exhaustiveness or have a function with multiple branches.

Example:

function convertToDist(x: Foo) {
  if (x.type === "a") {
    return x.asDist();
  } else if (x.type === "b") {
    return processXOfBType(x);
  }
}

In this case, I'd be tempted to type convertToDist(x: Foo): Dist explicitly, if either of these is true:

  • I don't want to write an extra else { throw new Error(``Unknown x type ${x.type satisifies never}`` }, and want to guarantee that this code won't break when x gets another possible type
  • I'm not confident that processXOfBType returns a dist (though then I'd prefer to make that function more reliable and leave this function as is)

In most other cases, I think implicit type inference is a better choice, especially if the return type is complex, which it tends to be, sometimes.

When you have to write something like : result<Dist, SqError> on all functions that pass that result around, that's basically copy-paste, and you would have to change the code in many more locations when you need to edit it.

Btw, if you prefer to write explicit types because hovering over values is annoying, there's a recent inlay hints option in VS Code that can show inferred types.


If we do write out types, I also would prefer to do it close to the location that creates the value (functions as a source of truth). With this generic parameter, we have to write ValueType at the first line of class definition, but the source of truth is get value(), which is declared separately.

Btw, my opinion on the value of this would change if you used it in multiple places. If we had not just get value(): ValueType, but also fromValue(v: ValueType), then sure, let's do it.


One last point: I realize that I don't remember why we need separate SqCalculatorValue vs SqCalculator, etc. They're parameterized by the same data, maybe we can simplify it.

* main:
  AST debugging view
  Create grumpy-owls-clean.md
  tooltips on import strings (injectable through renderImportTooltip)
  simplify some code
  ⬆️ Bump vite from 5.0.10 to 5.0.12
@OAGr OAGr marked this pull request as ready for review January 23, 2024 22:43
@OAGr OAGr merged commit c7a955b into main Jan 23, 2024
6 checks passed
@OAGr OAGr deleted the sqPath-refactor branch January 23, 2024 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

2 participants