I am implementing an API client, and the API is very connected, so if you have two types, responses, A and B, A contains a method for accessing its associated, B, and B contains a function to access its associated A, and there are numerous other types involved which have a similar relationship. A and B also have many fields in common, such as name and date or whatever.
At first I thought a bunch of mutually recursive records was an option but I’m not sure what to do about accessing all the overlapping fields. Recursive modules would be nice as they allow namespacing each type, but the amount of typing involved in recursive modules feels a lot.
The way I’m trying to represent things is the responses are records and they do not contain an instance of each other, but the URL to query the other then I have functions to take the input and return the other. I would like the methods namespaced with the type, though, otherwise I could do something like below:
module Type:
module A : sig
type t
end
module B : sig
type t
end
end
module Op : sig
module A : sig
val get_b : Type.A.t -> Type.B.t
end
module B : sig
val get_a : Type.B.t -> Type.A.t
end
end
Any other suggestions for the best way to approach this?
Have you considered using objects ? You could have a class type for the common fields, and then two mutually-recursive classes that extend it with the specific fields.
Using records, one approach is to use composition (in the OO sense) instead of extension:
type 'a common = {
<common fields>;
specific: 'a;
}
type specific_a = { ...; b : b }
and specific_b = { ...; a : a }
and a = specific_a common
and b = specific_b common
I am generally uninclined to use objects, maybe irrationally at this point. The record composition solution I think becomes unmanageable pretty quickly because the venn diagram of overlapping across the hundred or so of responses is pretty ridiculous.
What I have been playing with now is I have a module per record, and then modules for operations on the type. Something like:
module Type : sig
module Org : sig
type t
end
module Repo : sig
type t
end
module Issue : sig
type t
end
end
module Response : sig
type 'a t
val value : 'a t -> 'a
module Org : sig
type t = Type.Org.t
val issues_url : t -> Type.Issue.t list Request.t
val repos_url : t -> Type.Repo.t list Request.t
val url : t -> Uri.t
end
end
It looks a bit awkward but it’s not so bad and really users of the client I’m writing will never interact with the Type’s module.
What do you think? If you stumbled upon this API would be be like “what was this guy doing at the time???”