Comments (12)
Here's a minimal example that doesn't work:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveGeneric #-}
module Examples.Rec where
import Protolude hiding (Enum)
import GraphQL
import GraphQL.API
import GraphQL.Resolver (Handler)
type Directory = Object "Directory" '[] '[Field "subdirs" Directory]
directory :: Handler IO Directory
directory = pure directory
from graphql-api.
OK. I'm convinced. Removing this from the milestone, but think it should be top priority for next release—writing a file server should be obvious to any newbie.
from graphql-api.
FWIW, if we can figure out a plausible solution and have something hacky that demonstrates it, I'm OK with releasing without this being fixed.
However, if we can't prove that this is possible, then I'm reluctant to release. It's kind of a make-or-break feature, IMO.
from graphql-api.
For recursive types I don't think we'll get around newtyping. The following code doesn't work because it doesn't implement resolve
correctly (it needs to look through the selection set ss
and dispatch accordingly), but it shows how recursion can be implemented in principle:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses #-}
module Examples.Rec where
import Protolude hiding (Enum)
import GraphQL
import GraphQL.API
import GraphQL.Value.ToValue
import GraphQL.Resolver (Handler, HasResolver(..))
newtype Directory = R (Object "Directory" '[] '[Field "subdirs" Directory, Field "x" Text])
instance forall m. Monad m => HasResolver m Directory where
resolve _ ss = (pure . pure . toValue) ("broken" :: Text)
directory :: Handler IO Directory
directory = directory
example :: Text -> IO Response
example = interpretAnonymousQuery @Directory directory
from graphql-api.
This turns out to be more tricky than expected. Sticking to the directory
example I can't write the following instance because the type family computation is recursive, and therefore will never terminate:
instance forall m. Monad m => HasResolver m Directory where
type Handler m Directory = Handler m Directory :<> Handler m Text
from graphql-api.
Just to add more context: We also care about mutual recursion as pointed out in the opening comment of this issue. For pure self-recursion we could add a Self
combinator that gets transformed into recursive calls at the value level.
from graphql-api.
Here's a working example though I don't fully understand yet why this specific combination introduce recursion, as I am still calling resolve recursively (but that's probably it). I will try to extract the core of it into a nice interface (we don't need everything in that example).
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators, ScopedTypeVariables, RankNTypes #-}
{-# LANGUAGE DeriveGeneric, UndecidableInstances, StandaloneDeriving #-}
{-# LANGUAGE GADTs, KindSignatures, FlexibleInstances, MultiParamTypeClasses, TypeFamilies #-}
module Examples.Rec where
import Protolude hiding (Enum)
import GraphQL
import GraphQL.API
import GraphQL.Resolver
import Data.Aeson (encode)
newtype RD m = RD (Handler m Directory)
data R = R
type Directory = Object "Directory" '[] '[Field "subdirs" R, Field "name" Text]
instance forall m. (Monad m) => HasResolver m R where
type Handler m R = RD m
resolve (RD rd) = resolve @m @Directory rd
instance HasAnnotatedType R where
getAnnotatedType = getAnnotatedType @Int
directory :: Handler IO Directory
directory = pure (RD directory :<> pure "tom")
run :: Text -> IO Response
run = interpretAnonymousQuery @Directory directory
example :: IO LByteString
example = encode <$> run "{ subdirs { name subdirs { name subdirs { name } }} }"
-- "{\"data\":{\"subdirs\":{\"subdirs\":{\"subdirs\":{\"name\":\"tom\"},\"name\":\"tom\"},\"name\":\"tom\"}}}"
from graphql-api.
And an example with mutual recursion - the trick is to include a judiciously placed loop-breaker that's an actual data type with a constructor, which is how mutual recursion works in Haskell of course.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators, ScopedTypeVariables, RankNTypes #-}
{-# LANGUAGE DeriveGeneric, UndecidableInstances, StandaloneDeriving #-}
{-# LANGUAGE GADTs, KindSignatures, FlexibleInstances, MultiParamTypeClasses, TypeFamilies #-}
module Examples.Rec where
import Protolude hiding (Enum)
import GraphQL
import GraphQL.API
import GraphQL.Resolver
import Data.Aeson (encode)
newtype RecIssue m = RecIssue (Handler m (Issue m))
type User m = Object "User" '[] '[Field "issues" (List (RecIssue m)), Field "name" Text]
type Issue m = Object "Issue" '[] '[Field "owner" (User m), Field "title" Text]
instance forall m. (Monad m) => HasResolver m (RecIssue m) where
type Handler m (RecIssue m) = RecIssue m
resolve (RecIssue rd) = resolve @m @(Issue m) rd
instance forall m. HasAnnotatedType (RecIssue m) where
getAnnotatedType = getAnnotatedType @Int
issues :: Handler IO (Issue IO)
issues = pure (user :<> pure "issue-title")
user :: Handler IO (User IO)
user = pure (pure [RecIssue issues] :<> pure "tom")
run :: Text -> IO Response
run = interpretAnonymousQuery @(User IO) user
example :: IO LByteString
example = encode <$> run "{ issues { owner { name } } }"
-- "{\"data\":{\"issues\":[{\"owner\":{\"name\":\"tom\"}}]}}"
from graphql-api.
Interesting!
from graphql-api.
Note that HasAnnotatedType
returns garbage, but that doesn't matter because we aren't really using that information yet.
If HasAnnotatedType
used getAnnotatedType @Directory
it'd lead to infinite recursion. In order to break that we need to change the code to recognise cycles by some identifier (e.g. the object name) and then store a pointer. This would imply globally unique object names which I think is something that facebook do.
from graphql-api.
You mean globally unique type names, right? That's a requirement for a valid schema.
We'll need HasAnnotatedType
for introspection.
from graphql-api.
"Type" is overloaded :). I specifically mean GraphQL types, i.e.:
type X = Object "X" '[] '[...]
^^^ this
from graphql-api.
Related Issues (20)
- Domain errors on Mutations HOT 1
- Deriving instance FromValue - guideline for using underscore in Record field names HOT 2
- Do the warnings 'No explicit implementation for ToValue' cause errors elsewhere
- Thinking about Subscriptions HOT 1
- ValidationError VariableTypeNotFound despite presence in the schema HOT 4
- Simplify code by using DuplicateRecordFields and NamedFieldPuns HOT 3
- Refactor Value kind definition
- Simplify Value related function
- Provide a way to pass initialValue between resolver calls HOT 1
- Should graphql's selection sets be subtypes or records in haskell (as opposed to JSON-like AST) ?
- Latest release points to 0.1.1 (not 0.3.0) HOT 1
- How to construct a complex Object from Record? HOT 6
- Intergrating GraphQL and Servent Generically HOT 1
- List of Object makes an error (Tried to treat object as if it were leaf field)
- Is this project still under development ? HOT 1
- Issue with parsing non-nullable types
- Parsing documents with comments HOT 1
- How Integrate with DB provider?
- Question regarding updated features HOT 2
- Build error in `src/GraphQL/Internal/Syntax/Parser.hs`: No instance for (ConvertText Text ByteString)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-api.