Stream: beginners

Topic: ✔ Understanding Abilities


view this post on Zulip hchac (Mar 04 2025 at 23:13):

Hello,

I'm trying to grok Abilities at the moment by looking at the Json example and trying to follow through the code in the stdlib, particularly the Decode parts, but I'm getting stuck trying to understand where decoder comes from in the from_bytes_partial function.

Just to have all the context in one place, let's start from this part in the Json example:

decoder = Json.utf8_with({ field_name_mapping: PascalCase })

where the type of utf8_with is:

utf8_with : { ... } -> Json

Json is an opaque type that implements the DecoderFormatting abilities. This means that anywhere in the code we can call the functions [u8, u16, ..., record, tuple] from DecoderFormatting ability on a value of this type (is this understanding correct?), which will then be translated to the decode_* equivalents from the Json type.

Next in the example comes:

decoded = Decode.from_bytes_partial(request_body, decoder)

for which the underlying code is:

from_bytes_partial : List U8, fmt -> DecodeResult val where val implements Decoding, fmt implements DecoderFormatting
from_bytes_partial = |bytes, fmt| decode_with(bytes, decoder, fmt)

and

decode_with : List U8, Decoder val fmt, fmt -> DecodeResult val where fmt implements DecoderFormatting
decode_with = |bytes, @Decoder(decode), fmt| decode(bytes, fmt)

Now in from_bytes_partial this decoder argument has come out of the blue. It didn't come from the parameters, and there's no standalone decoder function in Decode.roc that I can see. The only place I can image it comes from is the Decoding ability:

Decoding implements
    decoder : Decoder val fmt where val implements Decoding, fmt implements DecoderFormatting

But this decoder is being used as a value which is directly passed into decode_with, and then somehow destructured by decode_with. I'm having issues understanding where this decoder came from. I'm confused because this decoder is not being applied on a value of some type that implements Decoding and then the result of that (which would be of type Decoder val fmt) is what gets passed into decode_with, instead this ability function is just passed directly, which is then also somehow being destructured (how is it destructuring an ability function that's not concrete?).

Apologies if it's something obvious I'm missing. Hopefully the answer for this also helps any other newcomers who happen to be stuck understanding how this works, like myself.

Thanks.

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 00:27):

I agree, this is a definitely a bit magical. In fact, in the eventual replacement to abilities, this is much more explicit.

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 00:28):

All functions within an ability are directly callable

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 00:28):

So for example, if you need to decode a u8 as part of another decoder, you would write:
Decode.u8(...)

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 00:29):

If that function was being called within the Decode module itself, you wouldn't need the prefix, so it would just be u8(...)

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 00:29):

This is what is being seen with decoder

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 00:30):

We are referencing Decode.decoder which is referencing the abstract ability function here:

Decoding implements
    decoder : Decoder val fmt where val implements Decoding, fmt implements DecoderFormatting

view this post on Zulip hchac (Mar 05 2025 at 00:49):

Brendan Hansknecht said:

We are referencing Decode.decoder which is referencing the abstract ability function here:

Decoding implements
    decoder : Decoder val fmt where val implements Decoding, fmt implements DecoderFormatting

Ah interesting that you can pass an abstract ability function around like a value :sweat_smile: , yeah that's definitely what trips me up. I'm not seeing a direct path to how it turns into a concrete implementation by looking at the code. Not gonna keep pestering with questions though since you mentioned this will be replaced with something more explicit (hopefully I can grok the next impl easier :smile: ).

Thanks!

view this post on Zulip Notification Bot (Mar 05 2025 at 00:49):

hchac has marked this topic as resolved.

view this post on Zulip Brendan Hansknecht (Mar 05 2025 at 01:53):

The new tool will always link back to a specific module with implementation.

foo.encode(...) would look at the type of foo and call the function encode from that module. It would pass foo as the first arg of the function.

The other option is module(foo).decode(...) which is dispatching on the module of the type variable foo. Needed for when foo is the output type instead of an arg.


Last updated: Jul 06 2025 at 12:14 UTC