Correct Arity

I still do not like FHIR primitive extensions, but ok they are more usable in profiles

Previously

In I do not like FHIR primitive extensions I complained

They undermine the guarantuee that a 1..1 element (min 1 and max 1 means required) of a specific type will exist, making every element optional with potentially any type.

With powerful object oriented languages, you probably get some kind of Element object which normally behaves like a primitive string, bool, etc., but can also get/set extension. Maybe this just seems nice but is still tricky, or maybe it really is nice, idk.

In Gleam, primitive extensions mean wrapping every single string, bool, etc., in Primitive:

pub type Primitive(a) {
  Primitive(id: Option(String), ext: List(Extension), value: Option(a))
}

I mean alternatively, you could also generate a second field, so instead of

mything: Primitive(Float)

you do something like


mything: Float,
mything_: Primitive,

This is just to say, these seem like the two possible ways you slap extensions on all primitives in Gleam, and neither seems great.

But now, profiles

Handwaving away the mechanics of profiles and slicing, and how "ungainly" the primitive extension looks in json, here are the big improvements:

What this means is you can have reasonable types from primitive extensions in Gleam. For example for US Core QuestionnaireResponse, the questionnaire field looks like this

Which in Gleam becomes


pub type UsCoreQuestionnaireresponseQuestionnaire {
  UsCoreQuestionnaireresponseQuestionnaire(
    id: Option(String),
    extension: List(ct.Extension),
    questionnaire_display: Option(String),
    url: Option(String),
    value: Option(String),
  )
}

Overall, this UsCoreQuestionnaireresponseQuestionnaire looks like a reasonable type; it has the questionnaire_display and url fields we care about as normal fields. The underlying shape of the primitive extension sort of pokes through a bit. The original questionnaire field is now "value" which is not super clear, but eh, fine. And us_core_questionnaireresponse_to_json butchers the json and us_core_questionnaireresponse_decoder stiches it back together, but behind the scenes.

More annoying is (did you catch this just looking at the type?)

json -> type -> json roundtrip does not preserve extension order and id, which might be bad. We can probably fix that somehow, maybe wrap questionnaire_display and url in something, but it does feel a bit less slick compared to simply putting all the extensions in a list and saying ok now do get_questionnaire_display(extensions). But I want to try having fields for them, idk, it's a work in progress 🙃

The positive side is now we can modify a specific field. The base QuestionnaireResponse resource has a canonical link which must be URL to FHIR Questionnaire resource, whereas the profile says the link can, via extension:url, be to a non-FHIR questionnaire. Plus add display via extension:questionnaireDisplay. And again now that we're using a profile, the big improvement is we don't have to slap primitive extension on literally every field, and we can use the profile to agree with other systems on element structure.

History, just for fun

See FHIR JSON format questions by Graham Grieve (2018) and https://chat.fhir.org/#narrow/channel/179166-implementers/topic/JSON.20Format.20for.20Primitives.20.26.20extensions for some interesting backstory, but ofc it is what it is at this point

FHIR is a ✨platform✨

FHIR motivates customizing data structures and APIs:

there is wide variability between jurisdictions and across the healthcare ecosystem around practices, requirements, regulations, education and what actions are feasible and/or beneficial.

For this reason, the FHIR specification is a "platform specification" - it creates a common platform or foundation on which a variety of different solutions are implemented.

If the point of FHIR is to be a platform that you can twist to fit whatever use case, then primitive extensions make more sense. I still don't love them because, like, do you really need them over normal extension, plus the gross JSON, but primitive extensions do seem more usable in the context of profiles.