Stream: ideas

Topic: Inspect.toStr and back


view this post on Zulip Aurélien Geron (Sep 16 2024 at 10:46):

Numbers, lists, tuples, and records can all be converted to Str using Inspect.toStr, and conveniently this string can be copied back into the REPL or a .roc file, and you get back the original data. Great! Being able to easily move data around as text is incredibly useful, it's an underrated feature. It removes a lot of friction during debugging, and it's also great for communication (e.g., I can copy/paste an output in a chat or an email and people can work with the data structure immediately).

However, it's not the case for Dict:

» Dict.fromList [("a", 1), ("b", 2)] |> Inspect.toStr

"""
{"a": 1, "b": 2}
""" : Str

If you paste {"a": 1, "b": 2} into the REPL, it's a syntax error.

I think we should fix this particular issue, but more generally we should encourage the community to ensure that Inspect.toStr is valid Roc syntax for the original data, whenever possible.

Wdyt?

view this post on Zulip Luke Boswell (Sep 16 2024 at 11:43):

I think this sounds like a good idea :grinning:

view this post on Zulip Kilian Vounckx (Sep 16 2024 at 11:50):

One thing we'd have to watch out for is parentheses. Assuming Inspect.toStr returned "Dict.fromList [...]", it would need parentheses when in tags. E.g. the following type [Foo (Dict Str U64)] would need to parenthesise the Dict.fromList [...] in its Inspect representation, but other types might not need it.

Overall, I like the idea though!

view this post on Zulip Isaac Van Doren (Sep 16 2024 at 14:43):

Great idea!

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:00):

I kinda agree kinda don't

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:01):

This is not the purpose of inspect even if it is convenient

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:01):

The goal of inspect is to print the data in a useful way for he developer

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:01):

It is what is used by debug statements

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:02):

For many data types, the best way for printing will not match the underlying data structure or how it is constructed

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:03):

For example, I think printing a dict in the Dict.fromList [(k, v), ...] is just extra noise.

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:04):

That said, it still may be better than a syntax that is seen nowhere else in roc....not sure....

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:28):

As a more concrete example, I think that many different types (btrees, dict, vecmap, etc) will want to use Inspect.dict. They all will just want to print out the same way. As a generic dictionary.

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:30):

Note, we could attempt to make something like Inspect.toReplStr, but it would not know of various different types of dictionaries without expanding Inspect. So it would output all dictionaries as Dict.fromList [(k, v), ...] even if the dictionary was not a standard library dictionary.

view this post on Zulip Aurélien Geron (Sep 17 2024 at 07:15):

Good points @Brendan Hansknecht . In Python, there's __str__() for a nice and short string, and __repr__() for a string that can be pasted back into Python. And __str__() defaults to __repr__(). We could have something similar in Roc:

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 13:39):

Yeah. Also, inspect is design in a very flexible way with an API similar to encoding in serde. It can build up any intermediate type, not just strings.

At this point, I actually think that may have been over zealous and unnecessary. Once we finally manage to update encode and decode to be flexible like serde and have any intermediate format, we may actually want to simplify the inspect API significantly and make it only output strings. If you want a different or more complex type, there will always be encode.

With a simplified inspect, I think it would fit more naturally to model like repr. Or support repr and standard to str akin to what python does.

view this post on Zulip Richard Feldman (Sep 17 2024 at 13:44):

well the original reason to make it more flexible was so that we could do things like:

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 13:48):

Ah yeah. Those are all very useful and not compatible with raw strings unless the inspect output is reparsed.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 13:49):

Probably still need something smarter if we want to support a repr like case. Cause current inspect is like serde which is made for encoding specific primitives not any type (e.g. encode the primitive of a dictionary, not this specific dict implementation and how to generate it)

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 13:53):

Though this could just mean that smarter inspect is not really compatible with repr and if we want repr it needs a totally different mechanism. Cause repr really wants to encode a function call or tag application.

Also, repr at some point probably needs complex types, but inspect is all primitives. And always building up all types out of primitives will add tons of noise.

view this post on Zulip Jasper Woudenberg (Sep 17 2024 at 15:41):

I wonder if it's helpful to reframe this as a higher level ask: Make it easy to extract a value from a Roc program and inspect it in a repl. That could be really compelling for looking at big complicated values. And if the values are very large, even if the copy-and-paste approach would work, it'd probably still be annoying.

It kind of remind me of how browser dev tools allow you to right-click an HTML node and create a variable with a refer to it in the console.

No idea if this is a good idea, but suppose you could write in Roc dump someComplicatedValue:

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 15:44):

Yeah, there is definitely an important debugging story here in general

view this post on Zulip Richard Feldman (Sep 17 2024 at 16:18):

a thing we could talk about is having literal syntax for dicts and sets

view this post on Zulip Richard Feldman (Sep 17 2024 at 16:19):

I've been interested in that since 2018 but it never seemed important

view this post on Zulip Richard Feldman (Sep 17 2024 at 16:19):

it would allow for pattern matching on them directly

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 16:19):

Yeah, the weird part about Inspect.toStr with the literal syntax is that it would turn all lists/dicts/sets into the standard library List/Dict/Set.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 16:20):

So you could use it in the repl, but it would change the type.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 16:21):

For example:

LinkList := [Cons a LinkList, Nil] implements [
    Inspect: {
        # Implement as Inspect.list
    }
]

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 16:22):

That linked list type would be converted to a standard library list if you do dbg myLinkList and then paste that into the repl.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 16:22):

So this still is different from __repr__ in a very important way.

view this post on Zulip Peter Marreck (Sep 17 2024 at 16:42):

I would definitely like a literal syntax for dicts that is analogous to (but different than) that of records (granted I'm thinking about the relationship between Maps and Structs in Elixir here). Literal sets might be good too.

I also agree that wherever possible, if the representation of a given data structure as a String can be identical to the literal for that data structure, it should be so. Definitely close to serde. I recently worked with the Decimal library in Elixir and I noticed that its literal "inspect" form was just the actual code for the constructor for whatever its internal representation is (so for example Decimal.new(123.456)), which actually worked well (and made sense even though it was a little weird).

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 16:50):

I would hate it if a list of decimals printed like that. So I definitely think we need a separate repr from inspect. Where inspect is used if I write dbg myListOfDec

view this post on Zulip Isaac Van Doren (Sep 17 2024 at 18:26):

Literals for dicts and sets sound nice. I like the idea of being able to pattern match on them, particularly for sets.

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:26):

Not that there's a big overlap here, but this kind of thing always reminds me about how allowing arbitrary objects be serialised in Java is considered a huge mistake. Again, many of Java's issues don't apply here, but worth knowing about at least :smile:

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:28):

For the ability to destructure/pattern match on arbitrary data structures, I feel Scala has a pretty good solution: you just implement the unapply function for your data structure and voilá, it works! So you can destructure pretty much anything, and it doesn't require any special syntax :blush:

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:34):

unapply is basically just a way for the code to say "the user tried to destructure something with this structure. If that makes sense to you, then return that thing"

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:36):

It also means you can destructure something without exposing the internals of it, which is quite nice.

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:37):

Destructuring just becomes another API that you can control as you see fit

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 18:39):

I need to read I to how that works.

view this post on Zulip Sam Mohr (Sep 17 2024 at 18:40):

This seems like a nice feature (docs), except for one little detail:

If there is no match, a `scala.MatchError` is thrown:

view this post on Zulip Sam Mohr (Sep 17 2024 at 18:40):

So long as we are flaunting with runtime errors, I'm not a fan, even if it's a small chance

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:43):

I like this introduction: https://medium.com/wix-engineering/scala-pattern-matching-apply-the-unapply-7237f8c30b41

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 18:47):

I would be quite worried about the perf of that for data structures, but definitely an interesting idea.

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:47):

So long as we are flaunting with runtime errors, I'm not a fan, even if it's a small chance

To be clear, that example is where you are using unapply in a way where you are telling the compiler you are certain it works, because you're not giving a default case. This is something Scala allows, but it's not something we would have to copy I think.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 18:52):

Kasper Møller Andersen said:

Not that there's a big overlap here, but this kind of thing always reminds me about how allowing arbitrary objects be serialised in Java is considered a huge mistake. Again, many of Java's issues don't apply here, but worth knowing about at least :smile:

Just skimmed that. I think most of those issues are fixed in rust by serde. And we build off the serde model, so it should be a lot less of a problem. Inspect of course is different from encode and decode, but still would overlap in some concerns

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 18:54):

What are you worried about with regards to performance and unapply @Brendan Hansknecht? You can definitely write inefficient accessors like this, but it seems to me that it's the same concerns one would have when writing any kind of function.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 18:57):

You are modeling the match differently. Instead of it being a known at compile time detail, it is something generic and flexible.

Kinda like the difference between using monophorization at compile time or reflection at runtime.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 18:58):

But also, I think it is nice to know that the types support pattern matching are random access and quick to pattern match on. If you can pattern match on a linked list like a regular list, you may access the last element without realizing it is O(n).

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 19:00):

Ah yes, it definitely leaves more to runtime. I don't personally mind that users are able to pattern match on "slow" structures though, so long as those structures advertise their performance characteristics accurately at least.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 19:02):

That said, I'm not actually sure how unapply would be modeled in roc. Maybe it could still be monomorphized over somehow. Also, not sure how unapply deals with variables in pattern matching. Like ["a", newVar, ... as rest, newVar2, "z"]. Not sure how that pattern would actually map to an unapply on a linked list.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 19:03):

I don't personally mind that users are able to pattern match on "slow" structures though,

I don't mind as long as it is very explicitly not just a list/dict/etc builtin type.

view this post on Zulip Kasper Møller Andersen (Sep 17 2024 at 19:03):

I don't think Scala allows you that much control over list pattern matches. I think you can only take the "rest" of the list at the end, so Scala isn't as flexible as Roc on that front.

view this post on Zulip Brendan Hansknecht (Sep 17 2024 at 19:05):

Ah


Last updated: Jun 16 2026 at 16:19 UTC