Stream: beginners

Topic: <opaque> in dbg and Inspect


view this post on Zulip Scally (Nov 17 2024 at 19:58):

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).

view this post on Zulip Luke Boswell (Nov 17 2024 at 20:06):

You can find an example for making a custom inspect impl here https://www.roc-lang.org/examples/CustomInspect/README.html

view this post on Zulip Richard Feldman (Nov 17 2024 at 20:18):

you can also do Foo := { ...whatever ... } implements [Inspect] to get an automatic implementation

view this post on Zulip Scally (Nov 17 2024 at 20:35):

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.

view this post on Zulip Luke Boswell (Nov 17 2024 at 20:37):

You can use Inspect.toStr thing to stringify an Opaque thing.

view this post on Zulip Scally (Nov 17 2024 at 20:51):

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

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 22:15):

Oh, I see. You want it to stay opaque even in the request header

view this post on Zulip Scally (Nov 17 2024 at 22:16):

I'd like it opaque when logging the request object or anything similar but not when actually sending the request

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

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).

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 22:18):

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)

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 22:19):

That could be a record with a session field of the opaque type

view this post on Zulip Paul Stanley (Nov 21 2024 at 22:43):

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.

view this post on Zulip Luke Boswell (Nov 21 2024 at 22:44):

Maybe it's a compiler bug? it wouldn't surprise me if this was just an edge case that we haven't seen before

view this post on Zulip Paul Stanley (Nov 21 2024 at 22:48):

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>]"

view this post on Zulip Brendan Hansknecht (Nov 22 2024 at 04:54):

Yeah, compiler bug

view this post on Zulip Paul Stanley (Nov 22 2024 at 06:14):

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.

view this post on Zulip Anthony Bullard (Nov 22 2024 at 10:59):

Do you see the same bug if Color is not opaque?

view this post on Zulip Paul Stanley (Nov 23 2024 at 14:30):

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.

view this post on Zulip Sam Mohr (Nov 23 2024 at 14:33):

@Paul Stanley you're on the money, aliases can't have Ability impls, so this doesn't apply there

view this post on Zulip Paul Stanley (Nov 23 2024 at 14:46):

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.

view this post on Zulip Sam Mohr (Nov 23 2024 at 16:01):

I don't believe that's the intended design


Last updated: Jul 06 2025 at 12:14 UTC