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

Add debug built-in #1267

Merged
merged 10 commits into from
Dec 5, 2023
Merged

Add debug built-in #1267

merged 10 commits into from
Dec 5, 2023

Conversation

romac
Copy link
Member

@romac romac commented Nov 24, 2023

Closes: #1266

The debug built-in is a utility for aiding in debugging specifications by allowing the spec developer to print out a contextual message while executing a specification, whether in a run or in a test, along with a formatted representation of a value.

This can be useful when trying to understand the state of variables at specific points during the spec execution or while tracking down issues.

The debug function has the following signature:

def debug(header: str, value: a) -> a

The debug function prints both header and the value on the same line, preceded by a > character, and then returns the given value, which makes it easy to debug nested expressions without having the extract them into a variable first.

Example usage in the REPL:

>>> debug("a number:", 42)
> a number: 42
42

>>> debug("a record:", { foo: 32, bar: true })
> a record: { bar: true, foo: 32 }
{ bar: true, foo: 32 }

>>> debug("large record:", largeRecord)
> large record: {
  hello:
    {
      world:
        { foo: { bar: { baz: 42 }, toto: { tata: { tutu: "Hello, World" } } } }
    }
}
{
  hello:
    {
      world:
        { foo: { bar: { baz: 42 }, toto: { tata: { tutu: "Hello, World" } } } }
    }
}

>>> if (a * debug("nested", a + b) == 10) true else false
> nested 5
true

  • Tests added for any new code
  • Documentation added for any new functionality
  • Entries added to the respective CHANGELOG.md for any new functionality
  • Feature table on README.md updated for any listed functionality

The `debug` function is a utility designed to aid in debugging
specifications by allowing the spec developer to print out a
contextual message while executing a specification, whether in
a run or in a test, along with a formatted representation of a value.

This can be useful when trying to understand the state of
variables at specific points during the spec execution or
while tracking down issues.

The `debug` function has the following signature:

```
def debug(header: str, value: a) -> a
```

The `debug` function prints both header and the value on the same line,
preceded by a `>` character, and then returns the given value,
which makes it easy to debug nested expressions without
having the extract them into a variable first.

Example usage in the REPL:

```
>>> debug("a number:", 42)
> a number: 42
42

>>> debug("a record:", { foo: 32, bar: true })
> a record: { bar: true, foo: 32 }
{ bar: true, foo: 32 }

>>> debug("large record:", largeRecord)
> large record: {
  hello:
    {
      world:
        { foo: { bar: { baz: 42 }, toto: { tata: { tutu: "Hello, World" } } } }
    }
}
{
  hello:
    {
      world:
        { foo: { bar: { baz: 42 }, toto: { tata: { tutu: "Hello, World" } } } }
    }
}

>>> if (a * debug("nested", a + b) == 10) true else false
> nested 5
true
```
@romac romac changed the title Add debug utility function Add debug built-in Nov 24, 2023
Copy link
Collaborator

@bugarela bugarela left a comment

Choose a reason for hiding this comment

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

I love it! Thanks for getting this done, I'm sure it will be super useful.

I'd do three things before merging:

  1. Adding an entry to CHANGELOG.md
  2. Adding an integration test to io-cli-tests.md (i.e. evaluating a quint REPL shell invocation and checking that the debug line is sent to STDOUT)
  3. Add an entry to builtin.qnt for debug with a docstring (and running npm run api-docs to generate a Markdown). This is used for showing overlay documentation in the IDEs.

If you are interested in doing any of those, please go ahead! Otherwise, I can pick it up 😄

@romac
Copy link
Member Author

romac commented Nov 24, 2023

Thanks a lot for the pointers! I've done all three things, let me know if you want me to add more tests or expand on the documentation.

@romac
Copy link
Member Author

romac commented Nov 24, 2023

I wonder if it would make sense to switch the arguments around, to allow for writing the following in method-call style:

>>> 42.debug("value is")
> value is 42

The downside with this order is that when used as a function it now reads less well imho:

>>> debug(42, "value is")
> value is 42

I don't feel strongly either way, so will defer to you on that if you don't mind :)

@romac romac requested a review from bugarela November 24, 2023 14:13
quint/src/builtin.qnt Outdated Show resolved Hide resolved
quint/src/effects/builtinSignatures.ts Outdated Show resolved Hide resolved
@konnov konnov self-requested a review November 24, 2023 14:52
Copy link
Contributor

@konnov konnov left a comment

Choose a reason for hiding this comment

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

I like this feature! But I have my doubts about the operator name debug:

  • It only works for REPL. We add the prefix q:: to all internal or repl-specific operators.
  • The name misleading is a bit misleading. It's not really debugging anything. Quick alternatives from gdb: print, inspect, trace. Of course, I would prefer them to have the prefix q::.

this.applyFun(app.id, 2, (msg, value) => {
let columns = terminalWidth()
let valuePretty = format(columns, 0, prettyQuintEx(value.toQuintEx(zerog)))
console.log('>', msg.toStr(), valuePretty.toString())
Copy link
Contributor

Choose a reason for hiding this comment

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

Even though it works this way. Would not it be better to print the message via CompilerErrorTracker? We could probably introduce a debug/info level for this purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest leaving this as is for now, and replacing it with the proper logging lib along with all our other console prints when we get around to that. This is not a compilation error, so I wouldn't want to put it in the tracker for that.

@romac
Copy link
Member Author

romac commented Nov 24, 2023

  • It only works for REPL. We add the prefix q:: to all internal or repl-specific operators.

Oh I wasn't aware of that. I'll see if I can figure out how to do that.

  • The name debug is a bit misleading. It's not really debugging anything. Quick alternatives from gdb: print, inspect, trace. Of course, I would prefer them to have the prefix q::.

Good point! If you ask me I would go with inspect:

a) to avoid overloading the "trace" term too much which could be confusing/misleading
b) while print might be a good alternative, I think a print built-in should allow omitting the value argument altogether and only print the message in that case. Conversely, an argument can probably be made for allowing one to pass only the value without a message to debug but I hadn't considered that previously.

@konnov
Copy link
Contributor

konnov commented Nov 24, 2023

I also like inspect or q::inspect :)

@romac
Copy link
Member Author

romac commented Nov 24, 2023

Yeah I meant q::inspect :)

@shonfeder
Copy link
Contributor

Thanks for the useful addition!

We'll need to make sure we strip this out of the IR before sending to alache I think, but we can do that in a followup.

@bugarela
Copy link
Collaborator

I personally like debug (or q::debug) more than inspect or the other alternatives. But I don't feel too strong about it.

@bugarela
Copy link
Collaborator

How should we proceed here? Should we go with q::inspect, or do we want to have further sync/async discussions on this?

@konnov
Copy link
Contributor

konnov commented Nov 28, 2023

How should we proceed here? Should we go with q::inspect, or do we want to have further sync/async discussions on this?

I would vote for proceeding like that.

@thpani
Copy link
Contributor

thpani commented Nov 28, 2023

I personally like debug (or q::debug) more than inspect or the other alternatives. But I don't feel too strong about it.

I'd also prefer debug (or even log) over inspect, but I'm not gonna block on naming.

@konnov
Copy link
Contributor

konnov commented Dec 4, 2023

@romac do you need help in getting this one merged?

@romac
Copy link
Member Author

romac commented Dec 4, 2023

I was just waiting for the Quint team to decide on the final name, but if/once that's done I am happy to do the renaming if needed and prefix it with the q:: namespace :)

@romac
Copy link
Member Author

romac commented Dec 5, 2023

I merged main in and moved debug under the q:: namespace.

Shall we keep the name or do I rename it to something else?

Copy link
Collaborator

@bugarela bugarela left a comment

Choose a reason for hiding this comment

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

I talked to the team and we are going with q::debug! Thanks for the patience and for all the updates.

@bugarela bugarela merged commit 84c12a7 into informalsystems:main Dec 5, 2023
15 checks passed
@romac
Copy link
Member Author

romac commented Dec 5, 2023

Thanks for helping me see this PR through!

@romac romac deleted the romac/debug branch December 5, 2023 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add facility for printing evaluated expressions to the console while executing a spec
5 participants