Building up a %{} map type from smaller parts?

Hi! Is it possible to make have a @type for a map, with specific keys, that’s composed out of smaller pieces for the sake of readability and/or reuse?

For example, say you wanted to chop up a map:

@type user() :: %{
  id: String.t(),
  full_name: String.t(),
  birth_year: non_neg_integer(),
}

… into smaller pieces, that can be used to ‘build’ up a larger structure, like:

@type personDetails() :: %{ full_name: ..., birth_year: ... }
@type hasId :: %{ id: String.() }

# (I'm borrowing JS / Flow's syntax for this.)
@type user() :: %{
  ...hasId(),                   # the 'id' field from this type,
  ...personDetails(),           # the 'full_name' and 'birth_year' from this type,
  created_at: NaiveDateTime.t() # … and this extra field while we're at it.
}

This faculty exists in a few typed languages with structurally-typed ‘records’, like TypeScript, Flow, Elm and PureScript; It’s known in at least the first two as ‘intersections’ (as counter operation to ‘unions’) and in the latter two as ‘extensible records’.

(I know that I can have a ‘nested’ composition, by having something like @type user :: %{ identification: hasId(), person: personDetails() }, but I’m interested in ‘sibling’ or ‘flat’ composition.)

My motivations for this is partly describing a struct used for DB interaction, partly for describing a union where each option is made up of some common fields, and partly a mix of situations where being able to re-use parts of a map like this would have aided reuse and avoided ‘copy-paste’-style definition drift.

(Thanks!)

2 Likes

I’m not aware of anything out of the box, but you could probably write a macro to do it.

2 Likes