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

Module naming #19

Merged
merged 11 commits into from
Dec 19, 2016
Merged

Module naming #19

merged 11 commits into from
Dec 19, 2016

Conversation

teh
Copy link
Collaborator

@teh teh commented Dec 16, 2016

What do you think about this naming? In any case, we have a branch to discuss :)

@jml
Copy link
Collaborator

jml commented Dec 16, 2016

I've been thinking about this a bit.

We need to start from the perspective of the end user. What modules will they need to import? Are there pairs of modules that they'll always need to import together? What modules will they never need to import? What will they probably almost never need to import?

For example, I'm not sure I believe in the separation of Schema and Value. I'm not sure I don't either. I certainly don't know why a user would want them.

This is why I think starting work on something like the servant tutorial (and/or worked examples in the docs) would be a good thing to do now. It will teach us what our structure should be like.

Haddock has a Description field for modules. This shows up in the link to the module on the module index page. I think we should come up with such a description for all of our modules at the same time we come up with names. The description should say why someone should care about that module.

What I want isn't just better module names, but rather a good, clear, and coherent terminology for the whole project. Module names are simply the first terms that our users are exposed to.

Shorter ideas:

  • Let's move everything that's internal into an Internal package
  • I think API is more common Haskell module name than Api, and is to be preferred
  • Perhaps the TypedAPI & TypedSchema modules should be in a sub-package. Thus, GraphQL.Typed.API and GraphQL.Typed.Schema

I'm sure I had other thoughts, but I cannot recall them now. No doubt there'll be an opportunity 😏

@jml jml added this to the Initial release milestone Dec 16, 2016
@teh
Copy link
Collaborator Author

teh commented Dec 16, 2016

If you have some time: I added a skeleton tutorial can you test whether you can build it? I don't know how people use pip/python these days so you might have to install some tools.

@jml
Copy link
Collaborator

jml commented Dec 16, 2016 via email

Copy link
Collaborator

@jml jml left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really great start, thanks!

I don't know exactly what state we need it to be in before merging. My comments range in scope quite a great deal.

type HelloWorld = Object "HelloWorld" '[]
'[ Argument "greeting" Text :> Field "me" Text
]
```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lexer chokes on this for me. not sure there's much we can do about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, nothting we can do other than upstream fix and wait a year

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

actions** when the field hasn't been requested. We only run the action
if the query specifies the field. Note that lazy evaluation would not
work because of the monadic nature of handlers.
* Finally, we have to terminate each handler with `:<> ()`. This is an
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about providing a helper function that did:

fieldList xs = xs :<> ()

Which would make it look less weird.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea we did but I'm not sure we can write one. We probably need the representation to be right-side nested tupels (that's why :<> is infixr), but fieldList re-orders the tuple nesting. I.e.

fieldList (a :<> b) == (a :<> b) :<> ()

but we need

a :<> (b :<> ())

I had figured out why we need infixr but I forgot why. I might also be wrong.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yea, I remember: We want the left-most item to be a "normal" Field or Object, and the right hand side to be a composite so we can unpack handlers recursively.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. This is a big deal to implement and maybe not worth it for our users, so leave it here for this branch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing in #31 for further ergonomics discussion.


## The handler

We defined a corresponding handler via the `HandlerType m a` which takes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this, we should probably rename this to just Handler.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

GraphQL.Input
GraphQL.Output
GraphQL.Schema
GraphQL.TypeApi
GraphQL.TypedApi
GraphQL.TypedSchema
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How's this?

  • GraphQL.API -- How to define a GraphQL API (currently TypedSchema)
  • GraphQL.Server -- How to implement handlers for a GraphQL API (currently TypedApi)
  • GraphQL.Literals -- Literal values in GraphQL (currently Value)
  • GraphQL.Internal
    • Input
    • Output
    • Schema
    • Validation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 except for Literals because it has quite a different connotation (e.g. [1]). Not sure what else to call it though. Aeson has value in the Types module but that doesn't seem great either. Brainstorm:

  • Raw
  • Direct
  • Encoding
  • EncodedValue
  • Values
  • Representation
  • ValueTree

It's the in-memory intermediate representation before/after serialization. Not sure what the usual name is.

[1]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad you like most of these names. Do you want to do that rename in this PR? (I think you should, but am also happy to see a different PR).

Re Value, I think they sort of are literals, because they are derived from http://hackage.haskell.org/package/graphql-0.3/docs/Data-GraphQL-AST.html#g:4 but with Variable taken away. To put another way, a constructed Value cannot be resolved any further. It is terminal. I guess we could call it Terminal or Terms.

Leave Value as-is for now until we figure out something we both like.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -70,6 +71,11 @@ queryError = throwM . QueryError
data a :<> b = a :<> b
infixr 8 :<>

-- | Union type separation operator.
data a :<|> b = a :<|> b
infixr 8 :<|>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

* The `pure` in the function call allows us to **avoid running
actions** when the field hasn't been requested. We only run the action
if the query specifies the field. Note that lazy evaluation would not
work because of the monadic nature of handlers.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what that means. Can you construct an example (for my benefit, if not the docs), that demonstrates why laziness isn't enough?

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Main (main) where

import Protolude

foo :: IO ()
foo = putStrLn @Text "foo"

bar :: IO ()
bar = putStrLn @Text "bar"

main :: IO ()
main = do
  let actions = [foo, bar]
  let (Just action) = head actions
  action

Produces:

$ ./lazyactions 
foo

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your example is what we are doing right now. We only sequence (i.e. execute the field) like you do with action when the field name matches. The (fieldname, action) tuple is constructed in the lazy GHC execution model.

What I am trying to express is that we need the handlers to be monadic so we can do IO. Not sure how to express.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not following. The type of actions is [IO ()], whereas your example says that it has to be IO [IO (), IO ()] in order to not evaluate everything.

I still would like to see a minimal example demonstrating why you think laziness isn't enough (in fact--we should have integration tests demonstrating this behaviour--not hard to do!), but not necessary for this branch. We can leave the prose as is & refine later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outer IO:

IO [IO (), IO ()]
^^ 

is a once-per-object IO action, e.g. we could open a DB connection, or do a single query to satisfy all fields.

The inner IO actions are only executed if the field name matches. We should chat in person, I'm not sure where we talk past each other :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now. I produced the following minimal example to demonstrate:

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Main (main) where

import Protolude

foo :: IO Text
foo = do
  putStrLn @Text "foo"
  pure "foo"

bar :: IO Text
bar = do
  putStrLn @Text "bar"
  pure "bar"

allActions :: IO (Text, Text)
allActions = do
  putStrLn @Text "all actions"
  (,) <$> foo <*> bar

main :: IO ()
main = do
  result <- fst <$> allActions
  putStrLn $ "result = " <> result

I'd suggest something like:

Each handler is a separate monadic action so we only perform the side effects for fields present in the query.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, updated.


```haskell
handler :: HandlerType IO HelloWorld
handler = pure $ (\greeting -> pure (greeting <> " to me")) :<> ()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean if a user wanted to implement a totally pure GraphQL handler they could say Handler Identity HelloWorld?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly no, because I added a MonadIO constraint everywhere. It might be possible to remove that constraint, but I'm a bit unclear ATM what guarantees we need from the underlying modad.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR.

There are two things we can do that I can think of right now:

  • if we never use the IO nature of the monad, then we can reduce the constraint to Monad (after we drop MonadThrow)
  • we can have a natural transformation thing like servant.

Both too big for this PR.

@jml
Copy link
Collaborator

jml commented Dec 17, 2016

Can confirm that I can build sphinx docs, but cannot build the tutorial docs.

I've sent an email to the servant mailing list asking how they do it: https://groups.google.com/forum/#!topic/haskell-servant/qxNBq11adBI

As a way forward, I think we should:

  • get the sphinx machinery in asap (ideally integrating with CI but I can do that as follow up)
  • do whatever non-controversial renames there are, but don't sweat the rest (google doc perhaps?)
  • not worry too much about getting the tutorial right, as it's something we can iterate on (although I'm so glad you started on it, because it's already easier to think through some of this stuff). Main thing is that what's checked in is true & readable.

@jml
Copy link
Collaborator

jml commented Dec 17, 2016

Also docs/build/ needs to be added to .gitignore

@teh
Copy link
Collaborator Author

teh commented Dec 18, 2016

rebased, docs/build/ should be in a gitignore already - not working for you?

@jml
Copy link
Collaborator

jml commented Dec 19, 2016

rebased, docs/build/ should be in a gitignore already - not working for you?

Nope.

jml@wit:~/src/graphql-api (module-naming) (0)
$ git rev-parse HEAD
b80ebaa75ddfd1988632cb5bcd880ab3f8eb5372
jml@wit:~/src/graphql-api (module-naming) (0)
$ cat .gitignore 
/.stack-work
jml@wit:~/src/graphql-api (module-naming) (0)
$ git status
On branch module-naming
Your branch is up-to-date with 'origin/module-naming'.
nothing to commit, working tree clean

Copy link
Collaborator

@jml jml left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like a quick look again before merging.

@teh
Copy link
Collaborator Author

teh commented Dec 19, 2016

re .gitignore, see: https://github.com/jml/graphql-api/pull/19/files#diff-14b597b3f3e040934bdb59aa959344a7 - it's not the top-level .gitignore

@jml
Copy link
Collaborator

jml commented Dec 19, 2016

re .gitignore, see: https://github.com/jml/graphql-api/pull/19/files#diff-14b597b3f3e040934bdb59aa959344a7 - it's not the top-level .gitignore

Oops, my bad. Works for me now.

@teh teh merged commit dfdabeb into master Dec 19, 2016
@teh teh deleted the module-naming branch December 19, 2016 12:22
@jml jml mentioned this pull request Dec 19, 2016
@jml jml mentioned this pull request Jan 2, 2017
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

Successfully merging this pull request may close these issues.

None yet

2 participants