Stream: beginners

Topic: Type wrapping


view this post on Zulip Rick Hull (Jan 19 2026 at 01:10):

I am not sure what to call this, other than type wrapping. I am new to Roc and no more than intermediate level experience with strong (functional) types. My goal, which is possibly misguided, is to take a List(U8) and specialize it with a known length and intended usage. e.g. 32-byte sk, 32-byte pk, 64-byte sig

Under roc-alpha-4, I used opaque types with some success for this. But my focus is now roc-nightly. With the resources I've gathered for how roc-nightly works, I did a lot of exploratory testing with LLMs to determine how to solve this problem. Here are my findings:

https://gist.github.com/rickhull/733e6020e1995c0997016559e291b0cc

Single-variant tag unions - open type

PublicKey := [PublicKeyBytes(List(U8)), ..]
SecretKey := [SecretKeyBytes(List(U8)), ..]

Simple constructors - just wrap the bytes, no validation yet

make_public_key = |bytes| PublicKeyBytes(bytes)
make_secret_key = |bytes| SecretKeyBytes(bytes)

Single-variant tag unions are the idiomatic and working solution for type-safe wrappers in Roc. The pattern:

  1. Use open tag unions with .. for compatibility with Try.Ok
  2. Use pattern matching for extraction (verbose but type-safe)
  3. Get compile-time type safety - PublicKey ≠ SecretKey

This is how core types like Bool and Try work in Builtin.roc.

As to whether these findings are accurate, this is an open question for me. I am not here to burden the channel with fixing my LLM dalliances, but I would like some validation, positive or negative, if I am barking up the right tree. Thanks!

view this post on Zulip Anton (Jan 19 2026 at 11:16):

Do you want to be able to hide the internal bytes so that e.g. only some functions of a specific module can access the raw bytes?

view this post on Zulip Rick Hull (Jan 19 2026 at 11:36):

it's a chunk of data, but I want to associate a type with this chunk of data. the type says: it is Red, not Blue. it has this size. So now I have Red32, Red64, Blue32, Blue64. and the compiler can enforce that make_orange only takes Red32 and not any chunk of data.

e.g. At runtime, the user provides some user input. This could be the chunk of data. The roc program takes the user input and constructs a Red32 according to some validation rules. This can fail, and the user might be reprompted.

Is there something flawed or wrongheaded about this idea?

view this post on Zulip Rick Hull (Jan 19 2026 at 12:11):

from what I understand, a nominal tag union with a method bytes that returns List(U8) would be nice. I struggled to make this work.

view this post on Zulip Rick Hull (Jan 19 2026 at 12:43):

it's actually working great now. Currently I have a Signature, with methods create and bytes. there is only a single internal representation, List(U8), so I don't want to use from/to in the method names. I was thinking of create / resolve as a more generic interface. thoughts on method names?

view this post on Zulip Anton (Jan 19 2026 at 13:25):

Is there something flawed or wrongheaded about this idea?

Nope, you're good, it's a relatively common pattern. See also https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/

view this post on Zulip Anton (Jan 19 2026 at 13:29):

thoughts on method names?

I do not think that we have really built conventions around this

view this post on Zulip Rick Hull (Jan 19 2026 at 13:52):

i don't want to use unwrap as that feels overloaded. Signature "resolves" to a List(U8)[64] (my notation). but i think create and bytes reads naturally and intuitively

view this post on Zulip Rick Hull (Jan 19 2026 at 13:58):

I completely agree with "shotgun parsing" as an antipattern. have a defined "filter layer" at the front of the data pipeline that only lets "well formed objects" through.

view this post on Zulip Dan G Knutson (Jan 19 2026 at 14:26):

I've seen people use 'pack' and 'unpack' as the pair of names for this kind of thing

view this post on Zulip Rick Hull (Jan 19 2026 at 14:51):

ooh, that's a good one, kind of like wrap/unwrap but without overloading those terms. in ruby and perl (C?), pack and unpack are quite sophisticated in terms of constructing binary data, whereas I am just doing simple validation. interesting

view this post on Zulip Rachael Sexton (Jan 21 2026 at 19:17):

not sure if this is the right place... I'm working through Exercism and working on the Collatz one, and suddenly realized... I'm not sure how to fmap easily in Roc?

I'm doing this recursively, so in a when...is... block, i'm pattern matching on whether it's odd/even, and adding 1 to the recursive steps function call. In Haskell I might `even n = succ <$> steps (3*n+1)

But how can I add 1 to Ok(1) easily without changing the signature of steps to be Result U64 -> Result U64 to enable pattern matching, when I don't have fmap?

view this post on Zulip Rachael Sexton (Jan 21 2026 at 19:18):

(This is wrong, I know, but it should get the idea of what I'm trying to do)

steps : U64 -> Result U64 _
steps = |x|
    when x is
        0 -> Err
        1 -> Ok(0)
        _ if Num.is_even(x) -> steps(x/2) + 1
        _ -> steps(3*x + 1) + 1

view this post on Zulip Rick Hull (Jan 21 2026 at 23:37):

Hi Rachael, I wish I could help more. I can't tell at a glance, but are you on the new compiler or old compiler?

view this post on Zulip Claude Précourt (Jan 22 2026 at 02:35):

The Exercism track is still using the old compiler, isn’t it?

view this post on Zulip Rachael Sexton (Jan 22 2026 at 03:07):

Not sure, this is the roc-cli version it's unit tests are depending on
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br",

view this post on Zulip Rick Hull (Jan 22 2026 at 03:25):

Yes, almost certainly the stable Roc version documented on the website. Outside of my very limited comfort zone.

view this post on Zulip Anton (Jan 24 2026 at 10:52):

Hi @Rachael Sexton,
You can use the ? operator or Result.map_ok


Last updated: Feb 20 2026 at 12:27 UTC