-
Notifications
You must be signed in to change notification settings - Fork 60
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
Recursive fields: add Arg.fix
and Schema.fix
#199
Recursive fields: add Arg.fix
and Schema.fix
#199
Conversation
@@ -2,8 +2,7 @@ let yojson = | |||
( module struct | |||
type t = Yojson.Basic.json | |||
|
|||
let pp formatter t = | |||
Format.pp_print_text formatter (Yojson.Basic.pretty_to_string t) | |||
let pp = Yojson.Basic.pretty_print ?std:None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drive-by change for better diffs when testing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @anmonteiro! Thanks for implementing this 🙂 A couple of thoughts...
I think the type recursive
is unnecessarily constraining, as it does not allow mutually recursive types with differing source type parameters. I think it could work with a single type parameter 'a
. About the type name, my current favorite is fix
or fixpoint
, as it's so closely tied to the function of the same name.
Besides those details, what's previously held me back from landing something like this, is how to have a cohesive API design that encompasses interfaces and unions. I think it would be preferable to construct recursive types with unions and interfaces in a uniform manner, i.e. add fields interface
and union
to the recursive
/fixpoint
type. This would also improve a part of the current API, which I consider to be a sore point. There are some details I haven't thought through in regards to converting to an abstract value. I'll give it a think and back to you. Feel free to also take a stab at it 🙂
@andreas Thanks for the prompt review. I understand the limitation but I'm not sure how the type As for interfaces and unions, I agree it probably makes sense to explore its unification to this |
I just pushed a commit that makes the |
commit d95b99e adds |
Nice, I like the I think we need to test the proposed change with an example a group of mutually recursive types that include an interface, a union or both to make sure we address that complexity. It's not clear to me that it would work out well without having investigated it further. Currently when you mix recursion and abstract types it works, but it's quite ugly and tricky. It requires use of (* ... *)
and node = Schema.union "Node"
and tree_as_node = lazy (Schema.add_type node (Lazy.force tree))
and contents_as_node = lazy (Schema.add_type node (Lazy.force contents))
[@@@ocaml.warning "-5"]
let _ = Lazy.force tree_as_node
let _ = Lazy.force contents_as_node Maybe you can still get away with something like the above with the new However, the new fixpoint construction means that we could instead require members of an abstract type to be specified when it is constructed, rather than added later with (* ... *)
and node, MagicList.[tree_as_node; contents_as_node] = Schema.union "Node" ~types:AnotherMagicList.[tree; contents] The complexity is instead shifted to the return value of I hope this clarifies my thinking a bit more 🙂 |
Gotcha, I do understand your point now. Some thoughts:
|
I've had a chance to try this out with I'm inclined to move towards specifying members of an abstract type at creation time as a follow-up. This would eliminate the last uses of |
@andreas thanks for trying this out! Happy to remove union and update the readme. my concerns around dynamic schemas exist today even with the proposed approach. Let's say you want to define a self-recursive input object type with some dynamic arguments. Right now my code defines: type _ dynamic_args =
| DynamicArgs :
{ adapter : 'a -> 'args
; args : ('a, 'args) Gql.Arg.arg_list
}
-> 'a dynamic_args In the above, we're hiding the value of Gql.Arg.fix (fun recursive ->
recursive.obj
name
~fields:(fun self ->
let (DynamicArgs { args; adapter }) = get_dynamic_args_from_somewhere in
args)
~coerce:(adapter StringMap.empty)) Right now there are 2 issues I'm working around:
Hope this is helpful to understand the issue I'm facing. |
I don't necessarily see a way around the scope escape generally. I'll push a commit addressing the other suggestions shortly. |
Hey @andreas, gentle ping on this one. |
Thanks @anmonteiro! |
…d graphql-async (0.14.0) CHANGES: - Support `__typename` on subscriptions (andreas/ocaml-graphql-server#178) - Handle unknown fields for subscriptions (andreas/ocaml-graphql-server#178) - Add ocamlformat (andreas/ocaml-graphql-server#177) - Handle missing variables as null (andreas/ocaml-graphql-server#184) - Show default value in introspection query (andreas/ocaml-graphql-server#194) - Support block strings in the parser (andreas/ocaml-graphql-server#198) - Handle skip/include directives on fragment spreads (andreas/ocaml-graphql-server#200) - Improved handling of recursive arguments and objects (andreas/ocaml-graphql-server#199) - Fix websocket conflict (andreas/ocaml-graphql-server#206) - Update deprecated Fmt functions (andreas/ocaml-graphql-server#206) - Use Yojson `t` types instead of deprecated `json` type (andreas/ocaml-graphql-server#208) - Raise minimum `rresult` version (andreas/ocaml-graphql-server#209)
fixes #134 by implementing the proposed
fix
approach.