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.
I agree, this is a definitely a bit magical. In fact, in the eventual replacement to abilities, this is much more explicit.
All functions within an ability are directly callable
So for example, if you need to decode a u8 as part of another decoder, you would write:
Decode.u8(...)
If that function was being called within the Decode
module itself, you wouldn't need the prefix, so it would just be u8(...)
This is what is being seen with decoder
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
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!
hchac has marked this topic as resolved.
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