Target - Haskell

Quick Start

gugugu-haskell \
        --input=dir/containing/gugugu/definitions \
        --output=dir/containing/haskell/code \
        --module-prefix=Some.Prefix \
        --derivings=Eq,Show \
        --with-codec \
        --with-server \
        --with-client \
        ;

Module

Gugugu module is represented by Haskell module, with module name unchanged.

Types

Primitives

Gugugu Type

Haskell Type

Unit

()

Bool

Bool

Int32

Data.Int.Int32

Double

Double

String

Data.Text.Text

Maybe A

Maybe A

List A

Data.Vector.Vector A

Record Type

Record type are represented Haskell record with deriving clause specified in command line options.

data Book
  = Book
    { id   :: Int32
    , name :: String
    }

becomes

data Book
  = Book
    { bookId   :: Int32
    , bookName :: Text
    }
  deriving (Eq, Show)

Enum Type

Enum type are represented by Haskell sum type with deriving clause specified in command line options.

data Color
  = Red
  | Green
  | Blue

becomes

data Color
  = Red
  | Green
  | Blue
  deriving (Eq, Show)

Foreign Type

data DateTime
  {-# FOREIGN haskell Data.Time.LocalTime.LocalTime #-}

becomes

type DateTime = Data.Time.LocalTime.LocalTime

Encoder and Decoder

All types in this section are located in module Gugugu.Lang.Haskell.Runtime.Codec with default configuration.

class Encoding a where
  encode :: (EncoderImpl c r g f, Applicative f) => c -> a -> f ()

class Decoding a where
  decode :: (DecoderImpl c r g f, Applicative f) => c ->      f a

All types generated by gugugu will have instances for class Encoding and Decoding.

The c is a value you have to provide to describe how to encode and decode a value. You also have to provide the EncoderImpl or DecoderImpl instance.

EncoderImpl and DecoderImpl

There are two typeclasses named EncoderImpl and DecoderImpl which you should provide implementation to do the encoding/decoding. They have kind * -> * -> (* -> *) -> (* -> *) -> Constraint.

  • The c is the value used in encoding and decoding. Usually you can use a type simply data CodecImpl = CodecImpl.

  • The r is the serialized type encoding to or decoding from. e.g. ByteString.

  • The g is the functor applied to the final result. Usually has an instance of MonadError in case something goes wrong.

  • The f is the functor applied to the intermediate result. It must have an Applicative instance, and usually have instances of MonadError / MonadState.

You might find examples/lang/haskell/src/GuguguExamples/Codec/Json.hs useful to write EncoderImpl/DecoderImpl.

Most class members do not use r and g except the following two.

class ForeignEncodersImpl c f => EncoderImpl c r g f | c -> r g f where
  encodeValue :: Encoding a => c -> a -> g r
  -- example
  encodeValue c a = runEncoding $ encode c a
    where runEncoding :: f () -> g r
          runEncoding = undefined

class ForeignDecodersImpl c f => DecoderImpl c r g f | c -> r g f where
  decodeValue :: Decoding a => c -> r -> g a
  -- example
  decodeValue c r = runDecoding r $ decode c
    where runDecoding :: r -> f a -> g a
          runDecoding = undefined

For most implementation, which is used in the examples, there exists a t, which has an instance of MonadTrans, and \(f \cong t (g)\),

If you are using the StateT SomeState g as the f. For an encodeValue, you usually should

  • Provide an initial state

  • Feed it to execStateT

  • Transform the state returned to serialized type, r

For a decodeValue, you usually should

  • Transform the r into an initial state

  • Feed it to runStateT

  • Make sure the returned state did not go wrong

  • Return the decoded value returned by runStateT

Encode/Decode Record Type

class ForeignEncodersImpl c f => EncoderImpl c r g f | c -> r g f where
  encodeRecord :: Int -> (c -> a -> f ()) -> c -> a -> f ()
  encodeRecordField :: Encoding a => Int -> Text -> c -> a -> f ()

class ForeignDecodersImpl c f => DecoderImpl c r g f | c -> r g f where
  decodeRecord :: Int -> (c -> f a) -> c -> f a
  decodeRecordField :: Decoding a => Int -> Data -> c -> f a

The generated encoder/decoder for record type consists of a call to encodeRecord/decodeRecord with the number of fields. And the provided callback will call the encodeRecordField/decodeRecordField several times with indices and names of the fields.

Encode/Decode Enum Type

class ForeignEncodersImpl c f => EncoderImpl c r g f | c -> r g f where
  encodeEnum :: (a -> Int) -> (a -> Text) -> c -> a -> f ()

class ForeignDecodersImpl c f => DecoderImpl c r g f | c -> r g f where
  decodeEnum :: (Int -> Maybe a) -> (Text -> Maybe a) -> c -> f a

The generated encoder/decoder for enum type consists of a call to encodeEnum/decodeEnum. You should encode/decode the value with the name or the index.

Encode/Decode Primitive and Foreign Types

class ForeignEncodersImpl c f where
  -- foreign encoder members, e.g.
  -- encodeDateTime :: c -> LocalTime -> f ()

class ForeignDecodersImpl c f where
  -- foreign decoder members, e.g.
  -- decodeDateTime :: c -> f LocalTime

class ForeignEncodersImpl c f => EncoderImpl c r g f | c -> r g f where
  encodeMaybe :: Encoding a => c -> Maybe  a -> f ()
  encodeList  :: Encoding a => c -> Vector a -> f ()

  encodeUnit   :: c -> ()     -> f ()
  encodeBool   :: c -> Bool   -> f ()
  encodeInt32  :: c -> Int32  -> f ()
  encodeDouble :: c -> Double -> f ()
  encodeString :: c -> Text   -> f ()

class ForeignDecodersImpl c f => DecoderImpl c r g f | c -> r g f where
  decodeMaybe :: Decoding a => c -> f (Maybe  a)
  decodeList  :: Decoding a => c -> f (Vector a)

  decodeUnit   :: c -> f ()
  decodeBool   :: c -> f Bool
  decodeInt32  :: c -> f Int32
  decodeDouble :: c -> f Double
  decodeString :: c -> f Text

The primitive types and foreign types will generate functions like above. And the encoder/decoder simply calls the function you provide.

Client and Server

All types in this section are located in module Gugugu.Lang.Haskell.Runtime.Transport with default configuration.

module Hello where

foo :: FooReq -> IO FooRes
bar :: BarReq -> IO BarRes

becomes

class HelloModule a f g m where
  foo :: a -> f FooReq -> m (g FooRes)
  bar :: a -> f BarReq -> m (g BarRes)

mkHelloTransport :: ( DecoderImpl ca ra ha fa
                    , EncoderImpl cb rb hb fb
                    , HelloModule a f g m
                    )
                 => ca
                 -> cb
                 -> a
                 -> ServerTransport f g m ra rb ha hb

instance HelloModule (GuguguClient f g m ra rb ha hb) f g m where

The a can be used as the client when used in client code, or as the server implementation in server code.

The ra and ha is the serialized type and the functor used by request, The rb and hb is the serialized type and the functor used by response. They are usually the same type but not necessary.

Some typical use of f, g and m are list below.

f can be

  • Identity, when you just want to pass the value.

  • data WithMeta a = WithMeta SomeMeta a, when you want some metadata with your request, such as authentication data.

  • [], when you want to process many data in one request.

g can be

  • Identity, when you just want to pass the value.

  • data WithMeta a = WithMeta SomeMeta a, when you want to return some metadata to with your response, such as request ID, processed time, etc.

  • [], when you want to return many data in one request.

  • Either ErrorInfo. when you want error handling.

m can be

  • Identity, when you have a pure implementation.

  • IO, when you want to handle it with IO.

  • ContT r IO, if you want delimited continuation.

Warning

Do not use any type that cannot be converted into data WithMeta a = WithMeta SomeMeta a (such as [a]) as f or g if you want to work with other target that does not support polymorphism over higher-kinded types. Most targets do not support polymorphism over higher-kinded types.

Server Usage

data QualName a = QualName (Vector a) a deriving Show

type ServerCodecHandler f g m ra rb ha hb =
  forall a b. (ra   ->   ha  a )
           -> ( b   ->   hb rb )
           -> (f a  -> m (g  b))
           ->  f ra -> m (g rb)

type ServerTransport f g m ra rb ha hb =
     QualName Text
  -> ServerCodecHandler f g m ra rb ha hb
  -> Maybe (f ra -> m (g rb))

mkHelloTransport :: ( DecoderImpl ca ra ha fa
                    , EncoderImpl cb rb hb fb
                    , HelloModule a f g m
                    )
                 => ca
                 -> cb
                 -> a
                 -> ServerTransport f g m ra rb ha hb

mkHelloTransport converts an a into a ServerTransport f g m ra rb ha hb.

The ServerCodecHandler f g m ra rb ha hb is called with,

  • ra -> ha a, the request decoder

  • b -> ha rb, the response encoder

  • f a -> m (g b), the real handler you provided in the class instance

and should return f ra -> m (g rb), the handler with ra and rb with decoding/encoding handled.

Please consult examples/lang/haskell/app/jsonhttp-server.hs for how to use the it.

Client Usage

data QualName a = QualName (Vector a) a deriving Show

data GuguguClient f g m ra rb ha hb
  = forall ca cb fa fb. ( EncoderImpl ca ra ha fa
                        , DecoderImpl cb rb hb fb
                        )
 => MkGuguguClient ca cb (ClientTransport f g m ra rb ha hb)

type ClientTransport f g m ra rb ha hb =
  forall a b. (a -> ha ra)
           -> (rb -> hb b)
           -> QualName Text
           -> f a
           -> m (g b)

instance HelloModule (GuguguClient f g m ra rb ha hb) f g m where

Like ServerTransport, ClientTransport can handle request about type ra and return an response about type rb.

The ClientTransport is called with,

  • a -> ha ra, the request encoder

  • rb -> hb b, the response decoder

  • QualName Text, the function name

  • f a, the request

and should return m (g b), the response.

Please consult examples/lang/haskell/app/jsonhttp-client.hs for how to write a ClientTransport.

Command Line Options

Usage: gugugu-haskell (-i|--input INPUT) (-o|--output OUTPUT)
                      (-p|--module-prefix MODULE_PREFIX)
                      [-r|--runtime-module RUNTIME_MODULE]
                      [--derivings DERIVINGS] [--with-codec] [--with-server]
                      [--with-client] [--trans-module-code ARG]
                      [--trans-module-value ARG] [--trans-module-type ARG]
                      [--trans-func-code ARG] [--trans-func-value ARG]
                      [--trans-type-code ARG] [--trans-type-func ARG]
                      [--trans-field-code ARG] [--trans-field-value ARG]
                      [--trans-enum-code ARG] [--trans-enum-value ARG]
                      [--version]

Available options:
  -i,--input INPUT         the directory containing the definition files
  -o,--output OUTPUT       the directory to put the generated sources
  -p,--module-prefix MODULE_PREFIX
                           the package prefix, e.g. Some.Prefix
  -r,--runtime-module RUNTIME_MODULE
                           location of gugugu runtime
                           module (default: "Gugugu.Lang.Haskell.Runtime")
  --derivings DERIVINGS    deriving clause for data type, use comma to separate
                           multiples, e.g. Eq,Show
  --with-codec             pass this flag to generate codecs, default to false
  --with-server            pass this flag to generate server, default to false,
                           implies with-codec
  --with-client            pass this flag to generate client, default to false,
                           implies with-codec
  --trans-module-code ARG  module name transformer for code (default: id)
  --trans-module-value ARG module name transformer for value (default: snake)
  --trans-module-type ARG  module name transformer for type of
                           client/server (default: id)
  --trans-func-code ARG    function name transformer for code (default: id)
  --trans-func-value ARG   function name transformer for value (default: snake)
  --trans-type-code ARG    type name transformer for code (default: id)
  --trans-type-func ARG    type name transformer in function (default: id)
  --trans-field-code ARG   record field name transformer for code (default: id)
  --trans-field-value ARG  record field name transformer for
                           value (default: snake)
  --trans-enum-code ARG    enum name transformer for code (default: id)
  --trans-enum-value ARG   enum name transformer for
                           value (default: upper-snake)
  -h,--help                Show this help text
  --help-transformers      list available name transformers
  --version                show version