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?
I think this sounds like a good idea :grinning:
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!
Great idea!
I kinda agree kinda don't
This is not the purpose of inspect even if it is convenient
The goal of inspect is to print the data in a useful way for he developer
It is what is used by debug statements
For many data types, the best way for printing will not match the underlying data structure or how it is constructed
For example, I think printing a dict in the Dict.fromList [(k, v), ...] is just extra noise.
That said, it still may be better than a syntax that is seen nowhere else in roc....not sure....
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.
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.
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:
Inspect.toStr defaults to calling Inspect.toReprStrDict), we could have a short form Inspect.toStr.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.
well the original reason to make it more flexible was so that we could do things like:
Ah yeah. Those are all very useful and not compatible with raw strings unless the inspect output is reparsed.
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)
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.
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:
someComplicatedValue into /tmp/roc-dump-XXXXXXinspect your value by running: roc repl --from-dump /tmp/roc-dump-XXXXXXsomeComplicatedValue variable set to whatever it was in the programYeah, there is definitely an important debugging story here in general
a thing we could talk about is having literal syntax for dicts and sets
I've been interested in that since 2018 but it never seemed important
it would allow for pattern matching on them directly
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.
So you could use it in the repl, but it would change the type.
For example:
LinkList := [Cons a LinkList, Nil] implements [
Inspect: {
# Implement as Inspect.list
}
]
That linked list type would be converted to a standard library list if you do dbg myLinkList and then paste that into the repl.
So this still is different from __repr__ in a very important way.
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).
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
Literals for dicts and sets sound nice. I like the idea of being able to pattern match on them, particularly for sets.
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:
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:
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"
It also means you can destructure something without exposing the internals of it, which is quite nice.
Destructuring just becomes another API that you can control as you see fit
I need to read I to how that works.
This seems like a nice feature (docs), except for one little detail:
If there is no match, a `scala.MatchError` is thrown:
So long as we are flaunting with runtime errors, I'm not a fan, even if it's a small chance
I like this introduction: https://medium.com/wix-engineering/scala-pattern-matching-apply-the-unapply-7237f8c30b41
I would be quite worried about the perf of that for data structures, but definitely an interesting idea.
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.
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
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.
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.
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).
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.
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.
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.
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.
Ah
Last updated: Jun 16 2026 at 16:19 UTC