Skip to content

How to construct a complex Object from Record? #217

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

Open
Benjmhart opened this issue Mar 22, 2019 · 6 comments
Open

How to construct a complex Object from Record? #217

Benjmhart opened this issue Mar 22, 2019 · 6 comments

Comments

@Benjmhart
Copy link

Benjmhart commented Mar 22, 2019

I can't seem to construct a multi-field argument without several errors: Here's what I have so far:

type QuoteGQL = GQLAPI.Object "QuoteGQL" '[]
  '[ GQLAPI.Argument "symbol" Text :> GQLAPI.Field "globalQuote" QuoteR ]

type QuoteR =  GQLAPI.Object "QuoteR" '[] 
  '[ GQLAPI.Field  "symbol"           Text
   , GQLAPI.Field  "open"             Text
   , GQLAPI.Field  "high"             Text
   , GQLAPI.Field  "low"              Text
   , GQLAPI.Field  "price"            Text
   , GQLAPI.Field  "volume"           Text
   , GQLAPI.Field  "latestTradingDay" Text
   , GQLAPI.Field  "previousClose"    Text
   , GQLAPI.Field  "change"           Text
   , GQLAPI.Field  "changePercent"    Text
  ]

data QuoteRecord = QuoteRecord  { symbol              :: Text
                                , open                :: Text
                                , high                :: Text
                                , low                 :: Text
                                , price               :: Text
                                , volume              :: Text
                                , latestTradingDay    :: Text
                                , previousClose       :: Text
                                , change              :: Text
                                , changePercent       :: Text
                                } deriving (Eq, Show, Generic)

makeQuoteR :: QuoteRecord -> Maybe QuoteR
makeQuoteR qr = GQLV.objectFromList 
  [ ( "symbol",           TV.toValue $ symbol qr)
  , ( "open",             TV.toValue $ open qr) 
  , ( "high",             TV.toValue $ high qr) 
  , ( "low",              TV.toValue $ low qr) 
  , ( "price",            TV.toValue $ price qr) 
  , ( "volume",           TV.toValue $ volume qr) 
  , ( "latestTradingDay", TV.toValue $ latestTradingDay qr) 
  , ( "previousClose",    TV.toValue $ previousClose qr) 
  , ( "change",           TV.toValue $ change qr) 
  , ( "changePercent",    TV.toValue $ changePercent qr) 
  ]

makeQuoteGQL is failing to typeCheck with
Expected type: Maybe QuoteR
Actual type: Maybe (GQLV.Object' GQLV.ConstScalar)

How on Earth can I construct a QuoteR - I need to use the record type as an intermediary because GQLAPI.Object does not implement FromJSON

what am I missing for these more complex types?

@theobat
Copy link
Contributor

theobat commented Mar 22, 2019

So this is where I think something is wrong with how we do things in graphql-api, we don't have a way to automatically derive haskell records from/to graphql ASTs (hence my issue there: #211)

To answer your question, you can't create a QuoteR, it's a simple "alias" to a GQLV.Object' GQLV.ConstScalar, this means in particular that this type is never embodied in your graphql server, it's just resolved in its handler but that's about it.
If you want to resolve it, you can define its relevant Handler where you actually return pure makeQuoteGQL yourQuoteRecordbut that's the only place where you know the AST you've constructed in makeQuoteGQL is a QuoteR (in types it's just a Graphql value).

The reason why it works this way is, as far as I understand it, that subtyping in haskell is hard and subtyping is very much the idea of graphql, the AST (graphql Values in this project) way of doing it is a way to solve the problem, but as of today it creates A. a lot of boilerplate to create a schema in the GQL DSL and with the classical haskell way and B. a weird understanding for the layperson beginning with the lib (as opposed to something like Deriving GQL automatically like aeson does it for json)...

I wish we could define our schemas with plain haskell objects like:

data QuoteRecord = QuoteRecord  { symbol              :: Text
                                , open                :: Text
                                , high                :: Text
                                , low                 :: Text
                                , price               :: Text
                                , volume              :: Text
                                , latestTradingDay    :: Text
                                , previousClose       :: Text
                                , change              :: Text
                                , changePercent       :: Text
                                } deriving (Eq, Show, Generic, GQL)

But it's not possible in this lib (and seemingly won't be possible in the near future, but I might be missing something).

@Benjmhart
Copy link
Author

@theobat - I understand that it's a simple alias, however I can't even seem to construct the alias above - shouldn't I be able to build a GQLV.Object' GQLV.ConstScalar that IS a QuoteR using the makeQuoteR function?, and likewise to wrap it with a GQLV.Object' that is a QuoteGQL? I feel like I'm missing something in the construction

@theobat
Copy link
Contributor

theobat commented Mar 22, 2019

Well it's not even an alias, I was wrong (but it's the same problem for me, it's just worse than I thought) it's a schema definition, it's completely unrelated to the Object value that you constructed (which I tried to explain with my own words):

  • GQLV.Object' GQLV.ConstScalar is on the Value module
  • QuoteR is an instance of Object in the Schema
-- this is for what you built
data Value' scalar
  = ValueScalar' scalar
  | ValueList' (List' scalar)
  | ValueObject' (Object' scalar)
  deriving (Eq, Ord, Show, Functor)

-- this is for QuoteR
data Object (name :: Symbol) (interfaces :: [Type]) (fields :: [Type])

@theobat
Copy link
Contributor

theobat commented Mar 22, 2019

And by the way it seems like there's no constructor at all for the data Object of the schema. Which would very much align with what I said above, you cannot build a value for QuoteR. I don't think this is the right approach, but it's the one in this lib (and any alternative with a more intuitive embedding int the haskell type system brings its own set of problems).
@jml @teh if you care to comment, I'd be curious about what you think of all that :)

@Benjmhart
Copy link
Author

@theobat - thanks for confirming my suspicions, that there is no way to actually construct my type

@teh
Copy link
Collaborator

teh commented Mar 24, 2019

You are right @theobat - the only way to construct a result is via a handler.

The idea at the time IIRC was that we only wanted to execute a resolver when required, e.g. imagine change in QuoteRecord being more expensive than the other fields so you only want to compute change when the client requests that field.

If you mostly care about convenience I think it'd be possible to write a generic resolver for records because we already have one for sum types to enum here: https://github.com/haskell-graphql/graphql-api/blob/master/src/GraphQL/Internal/API/Enum.hs#L113

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

3 participants