I've noticed that when using opaque types they get written as <opaque>
when using dbg and Inspect to output them as strings.
This is quite nice when working with secrets like passwords or authentication tokens etc, however, is there any way to still log the underlying value for debugging?
The other way around, is there any way to go the other way and force an opaque string type into an API that would otherwise only accept strings and then get that behavior? (i.e. basic-cli's Http Request uses strings in it's header declarations but it would be nice to not straight up print them in logs by default).
You can find an example for making a custom inspect impl here https://www.roc-lang.org/examples/CustomInspect/README.html
you can also do Foo := { ...whatever ... } implements [Inspect]
to get an automatic implementation
Is there any way to get that opaque type into something that would expect a string then?
I'd like to create an Http Request that logs as the opaque type but sends the request with the correct string.
Also the Http Request definition requires a Str so it doesn't allow my opaque string-equivalent type.
You can use Inspect.toStr thing
to stringify an Opaque thing.
Doesn't that just insert the censored output into the string field though? What I'd need is for the real value to be sent with the request nontheless.
What I'd like is
Token := Str
secretTokenFromFile = @Token (#some fileread)
dbg secretTokenFromFile # => <opaque>
request = { Http.defaultRequest & url: "[...]" , headers: [(Http.header "Cookie" "session=$(Inspect.toStr secretTokenFromFile)"] }
dbg request # => {body: [], headers: [{key: "Cookie", value: "session=<opaque>"}], ...
Http.send request # => actually send the request but with session=515sfkde
Oh, I see. You want it to stay opaque even in the request header
I'd like it opaque when logging the request object or anything similar but not when actually sending the request
That would only work if the http headers were opaque or if there was at least a custom header for opaque strings (wouldn't be automatic though).
My gut feeling is that you should be logging something earlier in your chain that knows everything about the request and consider the actual http headers and such as your boundary to the platform (which can't be opaque)
That could be a record with a session field of the opaque type
I hope I'm not hijacking a thread inappropriately (and if so happy to start a new one), but I'm having some related difficulties, which I'm sure reflect my (very deep) ignorance of how implements
works. (At this point I'm cargo-cult coding to a considerable degree, so please forgive me.)
If I define a custom type as per the example (https://www.roc-lang.org/examples/CustomInspect/README.html) which implements Inspect via toInspector
, and then directly create custom types ... all works as expected.
But if I then alias that custom type (say, to use the example, Rainbow : Color
) and then have a function that returns the aliased type, then the custom inspector does not work on the aliased type, which appears simply as "<opaque>" again. I've tried adding various invocations of implements
to the alias, but the compiler is not happy (which doesn't surprise me because after all this is just an alias so ...)
What should I be doing?
This appears also to affect other abilities (is that the right jargon?). The same custom type implements Eq, but when it tries to deal with equality for a List
of that type which has been aliased, Roc complains that it can't generate the Eq
ability for the custom type. (As it happens the custom type just wraps a U32 ...) What's odd is that the compiler obviously "sees" the underlying custom type behind the alias, because it complains that it can't generate an implementation for that type.
Sorry if/that I'm being stupid.
Maybe it's a compiler bug? it wouldn't surprise me if this was just an edge case that we haven't seen before
If it helps at all here's a (sort of minimal) example based on the example:
Color := [
Red,
Green,
Blue,
]
implements [
Inspect { toInspector: colorInspector },
]
Rainbow : Color
makeRainbow : Str -> Rainbow
makeRainbow = \s ->
when s is
"Red" -> @Color Red
"Blue" -> @Color Blue
_ -> @Color Green
colorInspector : Color -> Inspector f where f implements InspectFormatter
colorInspector = \@Color color ->
when color is
Red -> Inspect.str "_RED_"
Green -> Inspect.str "_GREEN_"
Blue -> Inspect.str "_BLUE_"
listIdentity : List Str -> List Rainbow
listIdentity = \colors ->
List.map colors \c ->
when c is
"R" -> makeRainbow "Red"
"G" -> makeRainbow "Green"
_ -> makeRainbow "Blue"
expect
result = makeRainbow "Red"
Inspect.toStr result == "\"_Red_\""
expect
result = Inspect.toStr (listIdentity ["R", "B"])
result == "[\"_RED_\", \"_BLUE_\"]"
Producing (e.g.)
This expectation failed:
42│> expect
43│> result = Inspect.toStr (listIdentity ["R", "B"])
44│> result == "[\"_RED_\", \"_BLUE_\"]"
When it failed, these variables had these values:
result : Str
result = "[<opaque>, <opaque>]"
Yeah, compiler bug
Thanks! Weirdly exciting to have bumped up against a compiler bug and not a my-brain bug ... I get the impression (and I still don't really understand the details!) that the whole abilities/implement is about to turn into a different scheme, so presumably a bug that can be left to stew in its own juice.
Do you see the same bug if Color
is not opaque?
Sorry: I don't understand. My fault I'm sure. If Color is not a custom type but just an alias for something nice and ordinary, then I don't think there's a bug ... but there's neither a need nor (?) an ability to implement a custom inspector for it, is there? The problem seems to be when there's a custom type "hidden" behind a type alias.
@Paul Stanley you're on the money, aliases can't have Ability impls, so this doesn't apply there
What's effectively happening is that an ability defined on a custom type is _not_ "percolating down" (at least sometimes, it seems a bit inconsistent) to an _alias_ for that custom type. It's also happening with other abilities ... at least Eq. Though I can't seem to generate a nice minimal example, I'm afraid. But when I've got aliased custom types in a list, I've had to resort to a recursive hand-rolled comparison function which casts back to the underlying representation ... but perhaps that's by design.
I don't believe that's the intended design
Last updated: Jul 06 2025 at 12:14 UTC