Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
# greskell - Haskell binding for Gremlin graph query language

greskell is a toolset to build and execute [Gremlin graph query language](http://tinkerpop.apache.org/gremlin.html) in Haskell.

Contents:

- [The Greskell type](#the-greskell-type)
- [Build variable binding](#build-variable-binding)
- [Submit to the Gremlin Server](#submit-to-the-gremlin-server)
- [DSL for graph traversals](#dsl-for-graph-traversals)
- [Type parameters of GTraversal and Walk](#type-parameters-of-gtraversal-and-walk)
- [Restrict effect of GTraversal by WalkType](#restrict-effect-of-gtraversal-by-walktype)
- [Submit GTraversal](#submit-gtraversal)
- [Graph structure types](#graph-structure-types)
- [GraphSON parser](#graphson-parser)
- [Make your own graph structure types](#make-your-own-graph-structure-types)
- [Write and read properties to/from the graph](#write-and-read-properties-tofrom-the-graph)
    - [Write properties](#write-properties)
    - [Read properties](#read-properties)
    - [Embed property data types](#embed-property-data-types)


## Prelude

Because this README is also a test script, first we import common modules.

```haskell common
{-# LANGUAGE OverloadedStrings, TypeFamilies, GeneralizedNewtypeDeriving, UndecidableInstances #-}
import Control.Applicative ((<$>), (<*>))
import Control.Category ((>>>))
import Control.Monad (guard)
import Data.Monoid (mempty, (<>))
import Data.Text (Text)
import qualified Data.Aeson as A
import qualified Data.Aeson.KeyMap as KM
import qualified Data.Aeson.Types as A
import Data.Function ((&))
import Test.Hspec
```

To run the examples in this README, run `stack test test-readme`. See [test-readme directory](https://github.com/debug-ito/greskell/tree/master/test-readme) to see how this works.


## The Greskell type

At the core of greskell is the `Greskell` type. `Greskell a` represents a Gremlin expression that evaluates to the type `a`.

```haskell Greskell
import Data.Greskell.Greskell (Greskell, toGremlin)

literalText :: Greskell Text
literalText = "foo"

literalInt :: Greskell Int
literalInt = 200
```

You can convert `Greskell` into Gremlin `Text` script by `toGremlin` function.

```haskell Greskell
main = hspec $ specify "Greskell" $ do
  toGremlin literalText `shouldBe` "\"foo\""
```

`Greskell` implements instances of `IsString`, `Num`, `Fractional` etc. so you can use methods of these classes to build `Greskell`.

```haskell Greskell
  toGremlin (literalInt + 30 * 20) `shouldBe` "(200)+((30)*(20))"
```

## Build variable binding

Gremlin Server supports [parameterized scripts](http://tinkerpop.apache.org/docs/current/reference/#parameterized-scripts), where a client can send a Gremlin script and variable binding.

greskell's `Binder` monad is a simple monad that manages bound variables and their values. With `Binder`, you can inject Haskell values into Greskell.

```haskell Binder
import Data.Greskell.Greskell (Greskell, toGremlin)
import Data.Greskell.Binder (Binder, newBind, runBinder)

plusTen :: Int -> Binder (Greskell Int)
plusTen x = do
  var_x <- newBind x
  return $ var_x + 10
```

`newBind` creates a new Gremlin variable unique in the `Binder`'s monadic context, and returns that variable.

```haskell Binder
main = hspec $ specify "Binder" $ do
  let (script, binding) = runBinder $ plusTen 50
  toGremlin script `shouldBe` "(((__v0)))+(10)"
  binding `shouldBe` KM.fromList [("__v0", A.Number 50)]
```

`runBinder` function returns the `Binder`'s monadic result and the created binding.


## Submit to the Gremlin Server

To connect to the Gremlin Server and submit your Gremlin script, use [greskell-websocket](http://hackage.haskell.org/package/greskell-websocket) package.

```haskell submit
import Control.Exception.Safe (bracket, try, SomeException)
import Data.Foldable (toList)
import Data.Greskell.Greskell (Greskell) -- from greskell package
import Data.Greskell.Binder -- from greskell package
  (Binder, newBind, runBinder)
import Network.Greskell.WebSocket -- from greskell-websocket package
  (connect, close, submit, slurpResults)
import System.IO (hPutStrLn, stderr)

submitExample :: IO [Int]
submitExample =
  bracket (connect "localhost" 8182) close $ \client -> do
    let (g, binding) = runBinder $ plusTen 50
    result_handle <- submit client g (Just binding)
    fmap toList $ slurpResults result_handle

plusTen :: Int -> Binder (Greskell Int)
plusTen x = do
  var_x <- newBind x
  return $ var_x + 10

main = hspec $ specify "submit" $ do
  egot <- try submitExample :: IO (Either SomeException [Int])
  case egot of
    Left e -> do
      hPutStrLn stderr ("submit error: " ++ show e)
      hPutStrLn stderr ("  We ignore the error. Probably there's no server running?")
    Right got -> do
      hPutStrLn stderr ("submit success: " ++ show got)
      got `shouldBe` [60]
```

`submit` function sends a `Greskell` to the server and returns a `ResultHandle`. `ResultHandle` is a stream of evaluation results returned by the server. `slurpResults` gets all items from `ResultHandle`.


## DSL for graph traversals

greskell has a domain-specific language (DSL) for building Gremlin [Traversal](http://tinkerpop.apache.org/docs/current/reference/#traversal) object. Two data types, `GTraversal` and `Walk`, are especially important in this DSL.

`GTraversal` is simple. It's just the greskell counterpart of [GraphTraversal](http://tinkerpop.apache.org/javadocs/current/full/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html) class in Gremlin.

`Walk` is a little tricky. It represents a chain of one or more method calls on a GraphTraversal object. In Gremlin, those methods are called "[graph traversal steps](http://tinkerpop.apache.org/docs/current/reference/#graph-traversal-steps)." greskell defines those traversal steps as functions returning a `Walk` object.

For example,

```haskell GTraversal
import Data.Greskell.Greskell (toGremlin, Greskell)
import Data.Greskell.GTraversal
  ( GTraversal, Transform, Walk, source, sV,
    gHasLabel, gHas2, (&.), ($.)
  )
import Data.Greskell.Graph (AVertex)

allV :: GTraversal Transform () AVertex
allV = source "g" & sV []

isPerson :: Walk Transform AVertex AVertex
isPerson = gHasLabel "person"

isMarko :: Walk Transform AVertex AVertex
isMarko = gHas2 "name" "marko"

main = hspec $ specify "GTraversal" $ do
  toGremlin (allV &. isPerson &. isMarko)
    `shouldBe`
    "g.V().hasLabel(\"person\").has(\"name\",\"marko\")"
```

In the above example, `allV` is the GraphTraversal obtained by `g.V()`. `isPerson` and `isMarko` are method calls of `.hasLabel` and `.has` steps, respectively. `(&.)` operator combines a `GTraversal` and `Walk` to get an expression that the graph traversal steps are executed on the GraphTraversal.

The above example also uses `AVertex` type. `AVertex` is a type for a graph vertex. We will explain it in detail later in [Graph structure types](#graph-structure-types).

Note that we use `(&)` operator in the definition of `allV`. `(&)` operator from [Data.Function](http://hackage.haskell.org/package/base/docs/Data-Function.html) module is just the flip of `($)` operator. Likewise, greskell defines `($.)` operator, so we could also write the above expression as follows.

```haskell GTraversal
  (toGremlin $ isMarko $. isPerson $. sV [] $ source "g")
    `shouldBe`
    "g.V().hasLabel(\"person\").has(\"name\",\"marko\")"
```

## Type parameters of GTraversal and Walk

`GTraversal` and `Walk` both have the same type parameters.

```haskell
GTraversal walk_type start end
Walk       walk_type start end
```

`GTraversal` and `Walk` both take the traversers with data of type `start`, and emit the traversers with data of type `end`. We will explain `walk_type` [later](#restrict-effect-of-gtraversal-by-walktype).

`Walk` is very similar to function `(->)`. That is why it is an instance of `Category`, so you can compose `Walk`s together. The example in the previous section can also be written as

```haskell GTraversal
  let composite_walk = isPerson >>> isMarko
  toGremlin (source "g" & sV [] &. composite_walk)
    `shouldBe`
    "g.V().hasLabel(\"person\").has(\"name\",\"marko\")"
```

## Restrict effect of GTraversal by WalkType

The first type parameter of `GTraversal` and `Walk` is called "walk type". Walk type is a type marker to describe effect of the graph traversal. There are three walk types, `Filter`, `Transform` and `SideEffect`. All of them are instance of `WalkType` class.

- Walks of `Filter` type do filtering only. It takes input traversers and emits some of them. It does nothing else. Example: `.has` and `.filter` steps.
- Walks of `Transform` type may transform the input traversers but have no side effects. Example: `.map` and `.out` steps.
- Walks of `SideEffect` type may alter the "side effect" context of the Traversal object or the state outside the Traversal object. Example: `.aggregate` and `.addV` steps.

Walk types are hierarchical. `Transform` is more powerful than `Filter`, and `SideEffect` is more powerful than `Transform`. You can "lift" a walk with a certain walk type to one with a more powerful walk type by `liftWalk` function.

```haskell WalkType
import Data.Greskell.GTraversal
  ( Walk, Filter, Transform, SideEffect, GTraversal,
    liftWalk, source, sV, (&.),
    gHasLabel, gHas1, gAddV, gValues, gIdentity
  )
import Data.Greskell.Graph (AVertex)
import Data.Greskell.Greskell (toGremlin)
import Network.Greskell.WebSocket (Client, ResultHandle, submit)

hasAge :: Walk Filter AVertex AVertex
hasAge = gHas1 "age"

hasAge' :: Walk Transform AVertex AVertex
hasAge' = liftWalk hasAge
```

Now what are these walk types useful for? Well, it allows you to build graph traversals in a safer way than you do with plain Gremlin.

In Haskell, we can distinguish pure and non-pure functions using, for example, `IO` monad. Likewise, we can limit power of traversals by using `Filter` or `Transform` walk types explicitly. That way, we can avoid executing unwanted side-effect accidentally.

```haskell WalkType
nameOfPeople :: Walk Filter AVertex AVertex -> GTraversal Transform () Text
nameOfPeople pfilter =
  source "g" & sV [] &. gHasLabel "person" &. liftWalk pfilter &. gValues ["name"]

newPerson :: Walk SideEffect s AVertex
newPerson = gAddV "person"

main = hspec $ specify "liftWalk" $ do
  ---- This compiles
  toGremlin (nameOfPeople hasAge)
    `shouldBe` "g.V().hasLabel(\"person\").has(\"age\").values(\"name\")"

  ---- This doesn't compile.
  ---- It's impossible to pass a SideEffect walk to an argument that expects Filter.
  -- toGremlin (nameOfPeople newPerson)
  --   `shouldBe` "g.V().hasLabel(\"person\").addV(\"person\").values(\"name\")"
```

In the above example, `nameOfPeople` function takes a `Filter` walk and creates a `Transform` GTraversal. There is no way to pass a `SideEffect` walk (like `gAddV`) to `nameOfPeople` because `Filter` is weaker than `SideEffect`. That way, we can be sure that the result traversal of `nameOfPeople` function never has any side-effect (thus its walk type is just `Transform`.)


## Submit GTraversal

You can submit `GTraversal` directly to the Gremlin Server. Submitting `GTraversal c s e` yeilds `ResultHandle e`, so you can get the traversal results in a stream.

```haskell WalkType
getNameOfPeople :: Client -> IO (ResultHandle Text)
getNameOfPeople client = submit client (nameOfPeople gIdentity) Nothing
```


## Graph structure types

Graph structure interfaces in Gremlin are represented as type-classes in greskell. We have `Element`, `Vertex`, `Edge` and `Property` type-classes for the interfaces of the same name.

The reason why we use type-classes is that it allows you to define your own data types as a graph structure. See ["Make your own graph structure types"](#make-your-own-graph-structure-types) below in detail.

As the basis of graph structure types, we have `AVertex`, `AEdge`, `AVertexProperty` and `AProperty` types. You might need those types because some functions are too polymorphic for the compiler to infer the types for its "start" and "end".

```haskell monomorphic
import Data.Greskell.Greskell (toGremlin)
import Data.Greskell.Graph (AVertex)
import Data.Greskell.GTraversal
  ( GTraversal, Transform,
    source, (&.), sV, gOut, sV', gOut',
  )

main = hspec $ specify "monomorphic walk" $ do
  ---- This doesn't compile
  -- toGremlin (source "g" & sV [] &. gOut []) `shouldBe` "g.V().out()"

  -- This compiles, with type annotation.
  let gv :: GTraversal Transform () AVertex
      gv = source "g" & sV []
      gvo :: GTraversal Transform () AVertex
      gvo = gv &. gOut []
  toGremlin gvo `shouldBe` "g.V().out()"
  
  -- This compiles, with monomorphic functions.
  toGremlin (source "g" & sV' [] &. gOut' []) `shouldBe` "g.V().out()"
```

In the above example, `sV` and `gOut` are polymorphic with `Vertex` constraint, so the compiler would complain about the ambiguity. In that case, you can add explicit type annotations of `AVertex` type, or use monomorphic versions, `sV'` and `gOut'`.


## GraphSON parser

`A` in `AVertex` stands for "Aeson". That means this type is based on the data type from [Data.Aeson](http://hackage.haskell.org/package/aeson/docs/Data-Aeson.html) module. With Aeson, greskell implements parsers for GraphSON.

[GraphSON](http://tinkerpop.apache.org/docs/current/dev/io/#graphson) is a format to encode graph structure types into JSON. [greskell-websocket](http://hackage.haskell.org/package/greskell-websocket) uses GraphSON to communicate with the Gremlin Server.

To support GraphSON decoding, we introduced the following symbols:

- `GraphSON` type: `GraphSON a` has data of type `a` and optional "type string" that describes the type of that data.
- `GValue` type: basically Aeson's `Value` enhanced with `GraphSON`.
- `FromGraphSON` type-class: types that can be parsed from `GValue`. It's analogous to Aeson's `FromJSON`.

`AVertex`, `AEdge`, `AVertexProperty` and `AProperty` types implement `FromGraphSON` instance, so you can directly obtain those types from the Gremlin Server.

```haskell WalkType
getAllVertices :: Client -> IO (ResultHandle AVertex)
getAllVertices client = submit client (source "g" & sV []) Nothing
```

Since greskell-1.0.0.0, `AVertex`, `AEdge` and `AVertexProperty` are just references to graph elements, and they don't keep any properties. To read properties from graph elements, see "[Read properties](#read-properties)" below.


## Make your own graph structure types

Often your graph data model is heterogeneous, that is, you have more than one types of vertices and edges with different meanings. Just using `AVertex` and `AEdge` for them easily leads to invalid graph operations. Let's distinguish them by Haskell's type system.

To make your own graph structure types, just wrap the base types with `newtype`.

```haskell own_types2
import Data.Greskell.Graph (AVertex, AEdge, ElementData, Element, Vertex, Edge)
import Data.Greskell.GraphSON (FromGraphSON)
import Data.Greskell.Greskell (toGremlin)
import Data.Greskell.GTraversal
  ( GTraversal, Walk, Transform, gOut, gOutE, gHasLabel,
    source, sV, (&.)
  )

-- | A @person@ vertex.
newtype VPerson = VPerson AVertex
        deriving (Eq,Show,FromGraphSON,ElementData,Element,Vertex)

-- | A @software@ vertex.
newtype VSoftware = VSoftware AVertex
        deriving (Eq,Show,FromGraphSON,ElementData,Element,Vertex)

-- | A @knows@ edge.
newtype EKnows = EKnows AEdge
        deriving (Eq,Show,FromGraphSON,ElementData,Element,Edge)

-- | A @created@ edge.
newtype ECreated = ECreated AEdge
        deriving (Eq,Show,FromGraphSON,ElementData,Element,Edge)
```

For each graph type, you need to derive `FromGraphSON`, `ElementData`, `Element` and `Vertex` or `Edge`. Note that you need to enable `GeneralizedNewtypeDeriving` and `UndecidableInstances` extensions of GHC to derive those instances.

With those graph element types, you can also define your own traversal steps.

```haskell own_types2
allPersons :: GTraversal Transform () VPerson
allPersons = source "g" & sV [] &. gHasLabel "person"

gOutKnows :: Walk Transform VPerson VPerson
gOutKnows = gOut ["knows"]

gOutCreated :: Walk Transform VPerson VSoftware
gOutCreated = gOut ["created"]

gOutEKnows :: Walk Transform VPerson EKnows
gOutEKnows = gOutE ["knows"]
```

Using those customized traversal steps, you can make your Gremlin scripts more type-safe and rich in semantics.

```haskell own_types2
main = hspec $ specify "own types" $ do
  toGremlin (allPersons &. gOutCreated)
    `shouldBe` "g.V().hasLabel(\"person\").out(\"created\")"

  toGremlin (allPersons &. gOutKnows)
    `shouldBe` "g.V().hasLabel(\"person\").out(\"knows\")"

  ---- This doesn't compile because the end of gOutCreated is VSoftware
  ---- but the start of gOutKnows is VPerson.
  -- toGremlin (allPersons &. gOutCreated &. gOutKnows)
  --   `shouldBe` "g.V().hasLabel(\"person\").out(\"created\").out(\"knows\")"
```

## Write and read properties to/from the graph

Writing and reading complex properties to/from the graph database is a little tricky. This section explains how we do that with greskell.

As a prelude for this section, we import the following modules first.

```haskell graph_io
import Control.Exception.Safe (bracket)
import Data.Foldable (toList)
import Data.Greskell.AsLabel (AsLabel)
import Data.Greskell.Binder (Binder, runBinder, newBind)
import Data.Greskell.Extra (writeKeyValues, (<=:>), (<=?>))
import Data.Greskell.Greskell (toGremlin)
import Data.Greskell.GraphSON (FromGraphSON(..), GValue)
import Data.Greskell.Graph
  ( AVertex, AEdge, ElementData, Element, Vertex, Edge,
    Key, Keys(KeysNil)
  )
import Data.Greskell.GTraversal
  ( Walk, GTraversal, SideEffect, Transform, liftWalk,
    source, sAddV, gValueMap, gProject, gByL,
    gOutV, gInV, gValues, sV, sV', sE,
    (<*.>), (&.), ($.), (<$.>),
    unsafeCastEnd,
    gHas2, gTo, gV, gAddE, gProperty, gDrop
  )
import Data.Greskell.PMap
  ( PMap, Multi, Single, PMapLookupException,
    lookupAs, lookupAs', pMapToFail
  )
import Network.Greskell.WebSocket
  ( connect, close, submit, submitPair,
    slurpResults, drainResults
  )

import Test.Hspec.NeedEnv (EnvMode(Want), needEnvHostPort)

main = hspec $ do
  specWrite
  specRead1
  specRead2
  specIO
```

### Write properties

To write properties of graph elements into the database, you use ".property" step of Gremlin. greskell offers some utility functions to make it a little easier.

First, define a data type for your application, and its corresponding vertex type.

```haskell graph_io
type Name = Text

data Person =
  Person
  { personName :: Name,
    personAge :: Int,
    personCompany :: Maybe Text
    -- ^ Name of the company the person works for, if any.
  }
  deriving (Show,Eq,Ord)

-- | A Vertex corresponding to 'Person'.
newtype VPerson = VPerson AVertex
        deriving (Eq,Show,FromGraphSON,ElementData,Element,Vertex)
```

Then, define `Key`s for properties of `Person`.

```haskell graph_io
keyName :: Key VPerson Name
keyName = "name"

keyAge :: Key VPerson Int
keyAge = "age"

keyCompany :: Key VPerson (Maybe Text)
keyCompany = "company"
```

We will use those `Key`s to write and read properties to/from the graph database. I know it's boring to define `Key`s manually like the above example. Future versions of greskell may support some ways to generate keys from a record type.

Anyway, once you set up the `Key`s, you can use `writeKeyValues` to make a series of ".property" steps for a `Person`.

```haskell graph_io
-- | Write 'Person' properties into a 'VPerson' vertex.
writePerson :: Person -> Binder (Walk SideEffect VPerson VPerson)
writePerson p = fmap writeKeyValues $ sequence $
                [ keyName <=:> personName p,
                  keyAge <=:> personAge p,
                  keyCompany <=?> personCompany p
                ]

-- | Add a new 'VPerson' vertex.
addPerson :: Person -> Binder (GTraversal SideEffect () VPerson)
addPerson p = writePerson p <*.> (pure $ sAddV "person" $ source "g")

specWrite :: Spec
specWrite =
  specify "property writers" $ do
    let p1 = Person "josh" 32 (Just "marko")
        (script1, binding1) = runBinder $ addPerson p1
    toGremlin script1 `shouldBe`
      "g.addV(\"person\").property(\"name\",((__v0))).property(\"age\",((__v1))).property(\"company\",((__v2))).identity()"
    binding1 `shouldBe`
      KM.fromList [ ("__v0", A.String "josh"),
                    ("__v1", A.Number 32),
                    ("__v2", A.String "marko")
                  ]
```

Note that properties in the Haskell program are sent to the Gremlin Server using variable binding. That is why we use `Binder` monad and monadic operators like `<=:>`, `<=?>` and `<*.>`.

Note also that we should use `<=?>` (not `<=:>`) to write an optional field `personCompany`. Basically TinkerPop's graph implementations don't allow writing "null" as a property value. So, if the optional field does not have a value, you should not generate ".property" step for it. The operator `<=?>` and `writeKeyValues` function take care of it.

```haskell graph_io
    let p2 = Person "peter" 35 Nothing
        (script2, binding2) = runBinder $ addPerson p2
    toGremlin script2 `shouldBe`
      "g.addV(\"person\").property(\"name\",((__v0))).property(\"age\",((__v1))).identity()"
    binding2 `shouldBe`
      KM.fromList [ ("__v0", A.String "peter"),
                    ("__v1", A.Number 35)
                  ]
```

### Read properties

The most basic way to read properties from the graph is to use ".valueMap" step. In greskell, you can use `gValueMap` function, which generates a `PMap` object as a result.

```haskell graph_io
personProps :: Walk Transform VPerson (PMap Multi GValue)
personProps = gValueMap KeysNil

specRead1 :: Spec
specRead1 = specify "property readers1" $ do
  toGremlin (source "g" & sV [] &. personProps) `shouldBe` "g.V().valueMap()"
```

`PMap` is a map of property key-values. You can use the `Key`s to get values from it.

```haskell graph_io
parsePerson :: PMap Multi GValue -> Either PMapLookupException Person
parsePerson pm =
  Person
  <$> (lookupAs keyName pm)
  <*> (lookupAs keyAge pm)
  <*> (lookupAs' keyCompany pm)
```

Note that you should use `lookupAs'` (not `lookupAs`) to read an optional field. `lookupAs'` treats lack of the key as `Nothing`, while `lookupAs` treats it as an error.

If you need more information than `gValueMap` can provide, you should probably use `gProject`. You often see such a case when you deal with data models for edges.

```haskell graph_io
data Knows =
  Knows
  { knowSubject :: Name,
    -- ^ Name of a person who knows
    knowObject :: Name,
    -- ^ Name of a person who is known
    knowWeight :: Double
  }
  deriving (Show,Eq,Ord)

-- | An Edge corresponding to 'Knows'.
newtype EKnows = EKnows AEdge
        deriving (Eq,Show,FromGraphSON,ElementData,Element,Edge)

keyWeight :: Key EKnows Double
keyWeight = "weight"
```

`knowSubject` and `knowObject` fields are not included in the properties of a "knows" edge, but they are properties of "person" vertices the edge connects. To get all information at once, `gProject` is useful.

```haskell graph_io
labelSubject :: AsLabel Name
labelSubject = "sub"

labelObject :: AsLabel Name
labelObject = "obj"

labelProps :: AsLabel (PMap Single GValue)
labelProps = "props"

knowsInfo :: Walk Transform EKnows (PMap Single GValue)
knowsInfo = gProject
            ( gByL labelSubject (gKnowSub >>> gValues [keyName]) )
            [ gByL labelObject  (gKnowObj >>> gValues [keyName]),
              gByL labelProps   (gValueMap KeysNil)
            ]
  where
    gKnowSub :: Walk Transform EKnows VPerson
    gKnowSub = gOutV
    gKnowObj :: Walk Transform EKnows VPerson
    gKnowObj = gInV

specRead2 :: Spec
specRead2 = specify "property readers2" $ do
  toGremlin (source "g" & sE [] &. knowsInfo)
    `shouldBe`
    ( "g.E().project(\"sub\",\"obj\",\"props\")"
      <> ".by(__.outV().values(\"name\")).by(__.inV().values(\"name\")).by(__.valueMap())"
    )
```

`gProject` takes one or more pairs of label and sub-traversal. Its result is a `PMap` where the key is the label and the value is the result of the sub-traversal. If you use `gValueMap` in a sub-traversal, its result `PMap` is nested.

To parse the result of `gProject`, you can use the labels and keys defined above.

```haskell graph_io
parseKnows :: PMap Single GValue -> Either PMapLookupException Knows
parseKnows pm =
  Knows
  <$> (lookupAs labelSubject pm)
  <*> (lookupAs labelObject pm)
  <*> (lookupAs keyWeight =<< lookupAs labelProps pm)
```

### Embed property data types

In the above examples, you cannot use property data types (`Person` and `Knows`) directly in greskell expressions. Instead, you first have to read out a `PMap` from the Gremlin Server, and then parse it into `Person` or `Knows`. Often it'd be more type-safe and semantic to read `Person` and `Knows` directly from the Gremlin Server.

To embed your property data types directly into greskell, you have to define `FromGraphSON` instance for them. That's acutally so easy, because we already define parsers for them.

```haskell graph_io
instance FromGraphSON Person where
  parseGraphSON gv = (pMapToFail . parsePerson) =<< parseGraphSON gv
```

In the above, `gv` is first parsed into a `PMap`, which is then parsed by `parsePerson`. `pMapToFail` just converts `Either` into `Parser`.

To make it type-safe, you should define a dedicated traversal to get a `Person` object.

```haskell graph_io
getPerson :: Walk Transform VPerson Person
getPerson = unsafeCastEnd personProps
```

`unsafeCastEnd` function converts the end type of the walk from `PMap` to `Person`. We know that `Person` is parsed from the `PMap`, so we can tolerate this unsafe cast here.

The same goes for `Knows`, too.

```haskell graph_io
instance FromGraphSON Knows where
  parseGraphSON gv = (pMapToFail . parseKnows) =<< parseGraphSON gv

getKnows :: Walk Transform EKnows Knows
getKnows = unsafeCastEnd knowsInfo
```

Now, by putting them all together, we can write and read data to/from the graph database like the following.

```haskell graph_io
addKnows :: Name -> Double -> Binder (Walk SideEffect VPerson EKnows)
addKnows target_name weight = do
  vtarget <- newBind target_name
  vweight <- newBind weight
  return $
    gAddE "knows" (gTo (gV [] >>> gHas2 keyName vtarget)) >>> gProperty keyWeight vweight

specIO :: Spec
specIO = specify "write and read graph properties" $ do
  -- Run this test only when we get host and port from the environment variables
  (host, port) <- needEnvHostPort Want "GRESKELL_TEST_README"
  
  bracket (connect host port) close $ \client -> do
    -- Clear graph.
    drainResults =<< submit client (gDrop $. liftWalk $ sV' [] $ source "g") Nothing

    -- Add and get a Person vertex.
    let input_p1 = Person "josh" 32 (Just "marko")
    drainResults =<< (submitPair client $ runBinder $ addPerson input_p1)
    got_p1 <- fmap toList $ slurpResults =<<
              submit client (getPerson $. sV [] $ source "g") Nothing
    got_p1 `shouldBe` [input_p1]

    -- Add another Person, add a Knows edge and get it.
    let input_p2 = Person "marko" 29 Nothing
        expected_k = Knows "marko" "josh" 1.0
    got_k <- fmap toList $ slurpResults =<<
             ( submitPair client $ runBinder $
               liftWalk getKnows <$.> addKnows "josh" 1.0 <*.> addPerson input_p2
             )
    got_k `shouldBe` [expected_k]
```

## Todo

- Complete graph traversal steps API.


## Author

Toshio Ito <[email protected]>