Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate required and optional parameters types #116

Open
chris-martin opened this issue Oct 22, 2024 · 0 comments
Open

Separate required and optional parameters types #116

chris-martin opened this issue Oct 22, 2024 · 0 comments

Comments

@chris-martin
Copy link
Contributor

chris-martin commented Oct 22, 2024

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
     , anotherRequiredPameter = 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant