Closed anmonteiro closed 3 years ago
@andreas Thanks for the prompt review.
I understand the limitation but I'm not sure how the type recursive
can take less type parameters. Could you expand a little more on what you were thinking? I'm gonna dig into it further and try to figure it out myself anyway.
As for interfaces and unions, I agree it probably makes sense to explore its unification to this fixpoint
API. I'm gonna play with it too. Do you think it makes sense to include it in this initial proposal or iterate on it over time?
I just pushed a commit that makes the fixpoint
type parameterized on just 'a
commit d95b99e adds interface
and union
fields in the fixpoint
definition too. I'm not sure if this is what you had in mind though.
Nice, I like the fixpoint
type a lot better now 🙂
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 lazy
, mutability, Schema.add_typ
and forcing lazy values with unit
return value. Here's an example from irmin
:
(* ... *)
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 fix
function, but I would like to confirm that and see how it looks.
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 Schema.add_typ
. This would mean we get rid of the mutability, lazy, etc. The above example would look something like the following:
(* ... *)
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 union
and interface
, which needs to return functions that can project values into the abstract source type. This is what I've tried to indicate with MagicList
and AnotherMagicList
above. I haven't yet tried implementing the above to understand the tradeoff better.
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 irmin-graphql
, and I think it's a net win, even with the inconsistencies for abstract types. I think union
should be removed from fixpoint
though, as it provides no value at this point. After that, I'm happy to merge this PR 🙂 (bonus points if you update the readme!)
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 lazy
in irmin-graphql
. I would appreciate if you can elaborate on your concerns though. I might have missed some limitations with this approach.
@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 'args
. How do I now create such type? Using the approach proposed in this PR, I could do something like:
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:
args
from the fields
function. OCaml taps out with:
Error: This expression has type
(Json.t StringMap.t, $DynamicArgsFields_'b1)
Graphql_lwt.Schema.Arg.arg_list
but an expression was expected of type
(Json.t StringMap.t, 'a) Graphql_lwt.Schema.Arg.arg_list
The type constructor $DynamicArgs_'b1 would escape its scope
self
, as it needs to be passed in outside the fields
functionHope 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!
fixes #134 by implementing the proposed
fix
approach.