I should preface this with the fact that I struggle to understand how the parser_for machinery and encodings work, and the indirection required to thread through the encodings and formats, and how parsing is made generic over the formats. Fortunately, (until now) I haven't had to, because the simple structural and nominal structs have mapped cleanly.
Here I have a case where the shape of the json can vary between multiple (2 for now) shapes, e.g., an error shape or a success shape, depending on the value of one of the fields. I.e. parsing into FallibleResponse(success_data, failure_data) := [Success(success_data), Failure(failure_data)], which by default has a different mapping to/from json.
It's not really clear to me from the docs how to achieve this, but I've got this far from just trying to satisfy the type checker before reaching a dead end:
FallibleResponse(success, failure) := [Success(success), Failure(failure)].{
parser_for : encoding -> (state -> Try({ value : FallibleResponse(a, b), rest : state }, []))
where [
a.parser_for : encoding -> (_ -> Try({ value : a, rest : _ }, _)),
b.parser_for : encoding -> (_ -> Try({ value : b, rest : _ }, _)),
encoding.parse : Str -> Try(_out, _errs),
]
parser_for = |encoding| |state| {
# Here I tried seeing if this method of dispatching would accept using parser_for on Peek
# parse_helper : encoding -> (state -> Try({ value : v, rest : state }, errs))
# where [v.parser_for : encoding -> (_ -> Try({ value : v, rest : _ }, _))]
# parse_helper = |enc| |st| {
# V : v
# V.parser_for(enc)(st)
# }
Peek : { type : Str }
peeked : { value : Peek, rest : state }
peeked = Peek.parser_for(encoding)(state)? ## <== Error here
if peeked.value.type == "error" {
B : b
parsed = B.parser_for(encoding)(state)?
Ok({ value: FallibleResponse.Failure(parsed.value), rest: parsed.rest })
} else {
A : a
parsed = A.parser_for(encoding)(state)?
Ok({ value: FallibleResponse.Success(parsed.value), rest: parsed.rest })
}
}
}
The error I can't get past is This parser_for method is being called on a value whose type doesn't have that method. on the structural type { type : Str } aka Peek.
For now I'm going to do something simple and just test each of the variants outside of the parser_for and use parse errors as a cue to try the alternate.
If I make Peek a nominal type and opt in with parser_for : _, the error about the missing parser_for becomes a warning: This declaration has a type annotation but no implementation..
Just realised this is on a 6 day old compiler version. (I swapped back from main because there seemed to be some cache instabilities this morning I couldn't get to reproducing, plus multi-minute roc check)
Hi @Jonathan,
I'm not an expert on the encoding stuff but I did get something working with Claude. Is this what you want?
Final :: [].{}
## The wire shape a response is inspected as before choosing a variant.
Peek : { type : Str }
## A JSON response that is either a success payload or an error payload,
## discriminated by its `type` field.
FallibleResponse(success, failure) := [Success(success), Failure(failure)].{
parser_for : _ -> (_ -> Try({ value : FallibleResponse(a, b), rest : _ }, [InvalidJson(Str), MissingRequiredField(Str), ..errs]))
where [
a.Json.Parseable([InvalidJson(Str), MissingRequiredField(Str), ..errs]),
b.Json.Parseable([InvalidJson(Str), MissingRequiredField(Str), ..errs]),
]
parser_for = |encoding| {
Success : a
Failure : b
parse_success = Success.parser_for(encoding)
parse_failure = Failure.parser_for(encoding)
parse_peek = Peek.parser_for(encoding)
|state| {
peeked = parse_peek(state)?
if peeked.value.type == "error" {
parsed = parse_failure(state)?
Ok({ value: FallibleResponse.Failure(parsed.value), rest: parsed.rest })
} else {
parsed = parse_success(state)?
Ok({ value: FallibleResponse.Success(parsed.value), rest: parsed.rest })
}
}
}
is_eq : _
}
Resp : FallibleResponse({ data : Str }, { message : Str })
expect {
result : Try(Resp, [InvalidJson(Str), MissingRequiredField(Str)])
result = Json.parse("{\"type\":\"error\",\"message\":\"boom\"}")
result == Ok(FallibleResponse.Failure({ message: "boom" }))
}
expect {
result : Try(Resp, [InvalidJson(Str), MissingRequiredField(Str)])
result = Json.parse("{\"type\":\"ok\",\"data\":\"hi\"}")
result == Ok(FallibleResponse.Success({ data: "hi" }))
}
expect {
result : Try(Resp, [InvalidJson(Str), MissingRequiredField(Str)])
result = Json.parse("{\"data\":\"hi\"}")
result == Err(MissingRequiredField("type"))
}
# Nested inside another derived shape, the custom parser is still picked up.
expect {
result : Try({ responses : List(Resp) }, [InvalidJson(Str), MissingRequiredField(Str)])
result = Json.parse("{\"responses\":[{\"type\":\"ok\",\"data\":\"a\"},{\"type\":\"error\",\"message\":\"b\"}]}")
result == Ok({ responses: [FallibleResponse.Success({ data: "a" }), FallibleResponse.Failure({ message: "b" })] })
}
That looks like something I tried except Peek.parser_for didn't seem to be derived for me. Perhaps because Json.Parseable makes it all click. Thanks though, I'll give it a try, but I wouldn't know why this in particular works :sweat_smile:
On another note, I don't normally use Claude but I let it rip on the roclang repo, given the circumstances. Told me it couldn't be done - in general it's been interesting trying to get LLMs to understand the more funky static dispatch stuff.
I went with Fable 5.1 on xhigh for this question in case it helps
Wow, how is Fable 5.1 xHigh? I've never tried it
Excellent so far, I have not used it much yet though. For most of my Roc things I use opus xhigh.
I've been trending the other direction and I basically use Low thinking for everything
I'll bust out a higher thinking setting if I'm wanting some kind of analysis, like developing a planning document
Interesting, intuitively I would expect thinking to help for debugging but I have not done any comparative tests.
I feel like we've crossed a threshold with these frontier models where their baseline is so good we don't need the extended reasoning.
Like I've been doing it for months now and barely notice the difference
For agentic coding on Fable 5.1 it does make a significant difference:
![]()
I guess my feeling is that 65% is good enough
Last updated: Sep 24 2026 at 15:59 UTC