For each record type e.g. Get_foo that represents a set of query parameters, the generator also produces a function mkGet_foo whose parameters are only the values of Get_foo that are required. This can be nice compared to using the Get_foo constructor directly because when an API adds new optional query parameters to a resource, that doesn't have to be a breaking change to users of the client. However, we've found some drawbacks:
- Since
mkGet_foo uses positional arguments rather than named record fields, it's easy to accidentally transpose the arguments if they are of the same type e.g. Text.
- We've had one bug where we constructed a value using
mkGet_foo and immediately followed by a Get_foo record update that overwrote one of the required fields that mkGet_foo had set. There's no indication in the types and no easy way from looking at the user code to see which mkGet_foo parameters correspond to which Get_foo record fields.
I'd like to throw out an idea:
-
For each Get_foo type, generate two corresponding record types
Required_Get_foo
Optional_Get_foo
which divide up the required and optional parameters respectively
-
Change mkGet_foo (or add a different function) to build a Get_foo from a Required_Get_foo and an Optional_Get_foo.
-
Define a constant Optional_Get_foo that has Nothing for all the fields, any of the following would work:
- a separate named constant for each parameter set;
mempty (if a semigroup operation is also wanted)
- def
I think both Monoid and Default have the advantage that they can be derived with generics rather than writing yet more template haskell.
So parameter construction then could go from
parameters :: Get_foo
parameters = (mkGet_foo a b){ someOptionalParameter = c }
to:
parameters :: Get_foo
parameters =
mkGet_foo'
Required_Get_foo
{ requiredParameter = a
, anotherRequiredParameter = b
}
$ def
{ someOptionalParameter = c
}
That way we have the advantages of both the mk function and of record construction:
- All the parameters are given explicitly by name.
- Adding optional parameters doesn't cause a breaking change at the use site.
One additional thought: To limit the explosion in the number of new generated identifiers introduced, a type class with an associated data family might be a nice way to go about it.
class Parameters a where
data RequiredParameters a :: Type
data OptionalParameters a :: Type
defaultOptionalParameters :: OptionalParameters a
mkParameters :: RequiredParameters a -> OptionalParameters a -> a
Then adding support for this feature is mostly just a matter of generating Parameters instances.
For each record type e.g.
Get_foothat represents a set of query parameters, the generator also produces a functionmkGet_foowhose parameters are only the values ofGet_foothat are required. This can be nice compared to using theGet_fooconstructor directly because when an API adds new optional query parameters to a resource, that doesn't have to be a breaking change to users of the client. However, we've found some drawbacks:mkGet_foouses positional arguments rather than named record fields, it's easy to accidentally transpose the arguments if they are of the same type e.g.Text.mkGet_fooand immediately followed by aGet_foorecord update that overwrote one of the required fields thatmkGet_foohad set. There's no indication in the types and no easy way from looking at the user code to see whichmkGet_fooparameters correspond to whichGet_foorecord fields.I'd like to throw out an idea:
For each
Get_footype, generate two corresponding record typesRequired_Get_fooOptional_Get_foowhich divide up the required and optional parameters respectively
Change
mkGet_foo(or add a different function) to build aGet_foofrom aRequired_Get_fooand anOptional_Get_foo.Define a constant
Optional_Get_foothat hasNothingfor all the fields, any of the following would work:mempty(if a semigroup operation is also wanted)I think both
MonoidandDefaulthave the advantage that they can be derived with generics rather than writing yet more template haskell.So parameter construction then could go from
to:
That way we have the advantages of both the
mkfunction and of record construction:One additional thought: To limit the explosion in the number of new generated identifiers introduced, a type class with an associated data family might be a nice way to go about it.
Then adding support for this feature is mostly just a matter of generating
Parametersinstances.