pinch- An alternative implementation of Thrift for Haskell.

Copyright(c) Abhinav Gupta 2015
MaintainerAbhinav Gupta <>
Safe HaskellNone




Pinch defines machinery to specify how types can be encoded into or decoded from Thrift payloads.


Serializing and deserializing

Types that can be serialized and deserialized into/from Thrift values implement the Pinchable typeclass. Instances may be derived automatically using generics, or written out by hand.

The Pinchable typeclass converts objects into and from Value objects, which act as a direct mapping to the Thrift wire representation. A Protocol is responsible for converting Value objects to and from bytestrings.

The encode and decode methods may be used on objects that implement the Pinchable typeclass to get the wire representation directly.

+------------+   Pinchable                    Protocol    +------------+
|            |               +------------+               |            |
|            +----pinch------>            +---serialize--->            |
| Your Type  |               |  Value a   |               | ByteString |
|            <---unpinch-----+            <--deserialize--+            |
|            |               +------------+               |            |
|            |                                            |            |
|            +-------------------encode------------------->            |
|            |                                            |            |
|            <-------------------decode-------------------+            |
+------------+                                            +------------+

encode :: Pinchable a => Protocol -> a -> ByteString Source

Encode the given Pinchable value using the given Protocol.

>>> unpack $ encode binaryProtocol ["a" :: ByteString, "b"]

decode :: Pinchable a => Protocol -> ByteString -> Either String a Source

Decode a Pinchable value from the using the given Protocol.

>>> let s = pack [11,0,0,0,2,0,0,0,1,97,0,0,0,1,98]
>>> decode binaryProtocol s :: Either String [ByteString]
Right ["a","b"]


Thrift requests implicitly form a struct and responses implicitly form a union. To send/receive the request/response, it must be wrapped inside a Message. The Message contains information like the method name, the message ID (to match out of order responses with requests), and whether it contains a request or a response.

Requests and responses may be wrapped into Message objects using the mkMessage function. The message body can be retrieved back using the getMessageBody function. The encodeMessage and decodeMessage functions may be used to encode and decode messages into/from bytestrings.

Consider the service method,

User getUser(1: string userName, 2: list<Attribute> attributes)
  throws (1: UserDoesNotExist doesNotExist,
          2: InternalError internalError)

The request and response for this method implictly take the form:

struct getUserRequest {
  1: string userName
  2: list<Attribute> attributes
union getUserResponse {
  0: User success
  1: UserDoesNotExist doesNotExist
  2: InternalError InternalError

(Note that the field ID 0 is reserved for the return value of the method.)

Given corresponding data types GetUserRequest and GetUserResponse, the client can do something similar to,

let req = GetUserRequest "jsmith" []
    msg = mkMessage "getUser" Call 0 req
response <- sendToServer (encodeMessage msg)
case decodeMessage response of
    Left err -> handleError err
    Right msg -> case getMessageBody msg of
        Left err -> handleError err
        Right (res :: GetUserResponse) -> handleResponse res

Similarly, on the server side,

case decodeMessage request of
    Left err -> handleError err
    Right msg -> case messageName msg of
        "getUser" -> case getMessageBody msg of
            Left err -> handleError err
            Right (req :: GetUserRequest) -> do
                let mid = messageId msg
                res <- handleGetUser req
                return (mkMessage "getUser" Reply mid res)
                -- Note that the response MUST contain the same
                -- message ID as its request.
        _ -> handleUnknownMethod

encodeMessage :: Protocol -> Message -> ByteString Source

Encode the Message using the given Protocol.

let request = GetUserRequest (putField "jsmith") (putField [])
    message = mkMessage "getUser" Call 42 request
in encodeMessage binaryProtocol message

decodeMessage :: Protocol -> ByteString -> Either String Message Source

Decode a Message using the given Protocol.

>>> decodeMessage binaryProtocol bs >>= getMessageBody :: Either String GetUserRequest
Right (GetUserRequest {userName = Field "jsmith", userAttributes = Field []})


class IsTType (Tag a) => Pinchable a where Source

The Pinchable type class is implemented by types that can be sent or received over the wire as Thrift payloads.

Minimal complete definition


Associated Types

type Tag a Source

TType tag for this type.

For most custom types, this will be TStruct, TUnion, or TException. For enums, it will be TEnum. If the instance automatically derived with use of Generic, this is not required because it is automatically determined by use of Field or Enumeration.


pinch :: a -> Value (Tag a) Source

Convert an a into a Value.

For structs, struct, .=, and ?= may be used to construct Value objects tagged with TStruct.

unpinch :: Value (Tag a) -> Parser a Source

Read a Value back into an a.

For structs, .: and .:? may be used to retrieve field values.

data Parser a Source

A simple continuation-based parser.

This is just Either e a in continuation-passing style.

runParser :: Parser a -> Either String a Source

Run a Parser and return the result inside an Either.

Automatically deriving instances

Pinch supports deriving instances of Pinchable automatically for types that implement the Generic typeclass provided that they follow the outlined patterns in their constructors.

Structs and exceptions

Given the struct,

struct User {
  1: required string name
  2: optional string emailAddress

A Pinchable instance for it can be automatically derived by wrapping fields of the data type with the Field type and specifying the field identifier as a type-level numeral. Fields which hold a Maybe value are considered optional.

data User = User
    { userName         :: Field 1 Text
    , userEmailAddress :: Field 2 (Maybe Text)
  deriving (Generic)

instance Pinchable User

The DeriveGeneric extension is required to automatically derive instances of the Generic typeclass and the DataKinds extension is required to use type-level numerals.


As with structs and exceptions, fields of the data type representing a union must be tagged with Field, but to satisfy the property of a union that only one value is set at a time, they must be on separate constructors.

For example, given the union,

union Item {
  1: binary bin
  2: string str
  3: i32    int

A Pinchable instance can be derived like so,

data Item
    = ItemBin (Field 1 ByteString)
    | ItemStr (Field 2 Text)
    | ItemInt (Field 3 Int32)
  deriving (Generic)

instance Pinchable Item

The DeriveGeneric extension is required to automatically derive instances of the Generic typeclass and the DataKinds extension is required to use type-level numerals.

If the union represents the response of a service method which returns a void result, the type Void may be used.

data GetFooResponse
  = GetFooDoesNotExist  (Field 1 FooDoesNotExist)
  | GetFooInternalError (Field 2 InternalError)
  | GetFooSuccess Void

newtype Field n a Source

Fields of data types that represent structs, unions, and exceptions should be wrapped inside Field and tagged with the field identifier.

data Foo = Foo (Field 1 Text) (Field 2 (Maybe Int32)) deriving Generic
instance Pinchable Foo
data A = A (Field 1 Int32) | B (Field 2 Text) deriving Generic
instance Pinchable Foo

Fields which hold Maybe values are treated as optional. All fields values must be Pinchable to automatically derive a Pinchable instance for the new data type.


Field a 


Functor (Field n) 
Foldable (Field n) 
Traversable (Field n) 
Bounded a => Bounded (Field n a) 
Enum a => Enum (Field n a) 
Eq a => Eq (Field n a) 
Ord a => Ord (Field n a) 
Show a => Show (Field n a) 
Generic (Field n a) 
Monoid a => Monoid (Field n a) 
NFData a => NFData (Field n a) 
(Pinchable a, KnownNat n) => GPinchable (K1 i (Field n (Maybe a))) 
(Pinchable a, KnownNat n) => GPinchable (K1 i (Field n a)) 
type Rep (Field n a) 
type GTag (K1 i (Field n (Maybe a))) = TStruct 
type GTag (K1 i (Field n a)) = TStruct 

getField :: Field n a -> a Source

Gets the current value of a field.

let Foo a' _ = {- ... -}
    a = getField a'

putField :: a -> Field n a Source

Puts a value inside a field.

Foo (putField "Hello") (putField (Just 42))

field :: Functor f => (a -> f b) -> Field n a -> f (Field n b) Source

A lens on Field wrappers for use with the lens library.

person & name . field .~ "new value"

data Void Source

Represents a void result for methods.

This should be used as an element in a response union along with Field tags.

For a method,

void setValue(..) throws
  (1: ValueAlreadyExists alreadyExists,
   2: InternalError internalError)

Something similar to the following can be used.

data SetValueResponse
  = SetValueAlreadyExists (Field 1 ValueAlreadyExists)
  | SetValueInternalError (Field 2 InternalError)
  | SetValueSuccess Void
  deriving (Generic)

instance Pinchable SetValueResponse





Given the enum,

enum Op {
  Add, Sub, Mul, Div

A Pinchable instance can be derived for it by creating one constructor for each of the enum values and providing it a single Enumeration argument tagged with the enum value.

data Op
    = OpAdd (Enumeration 0)
    | OpSub (Enumeration 1)
    | OpMul (Enumeration 2)
    | OpDiv (Enumeration 3)
  deriving (Generic)

instance Pinchable Op

Note that you need to know the values assigned to the enums. If not specified, Thrift automatically assigns incrementing values to the items in the order they appear starting at 0.

The DeriveGeneric extension is required to automatically derive instances of the Generic typeclass and the DataKinds extension is required to use type-level numerals.

data Enumeration n Source

Data types that represent Thrift enums must have one constructor for each enum item accepting an Enumeration object tagged with the corresponding enum value.

data Role = RoleUser (Enumeration 1) | RoleAdmin (Enumeration 2)
  deriving Generic
instance Pinchable Role



enum :: Enumeration n Source

Convenience function to construct Enumeration objects.

let role = RoleUser enum

Manually writing instances

Instances of Pinchable can be constructed by composing together existing instances and using the .=, .:, etc. helpers.

Structs and exceptions

Given a Thrift struct,

struct Post {
  1: optional string subject
  2: required string body

The Pinchable instance for it will be,

data Post = Post
    { postSubject :: Maybe Text
    , postBody    :: Text

instance Pinchable Post where
    type Tag Post = TStruct

    pinch (Post subject body) =
        struct [ 1 ?= subject
               , 2 .= body

    unpinch value =
        Post <$> value .:? 1
             <*> value .:  2


Given a Thrift union,

union PostBody {
  1: string markdown
  2: binary rtf

The Pinchable instance for it will be,

data PostBody
    = PostBodyMarkdown Text
    | PostBodyRtf ByteString

instance Pinchable PostBody where
    type Tag PostBody = TUnion

    pinch (PostBodyMarkdown markdownBody) =
        union 1 markdownBody
    pinch (PostBodyRtf rtfBody) =
        union 2 rtfBody

    unpinch v = PostBodyMarkdown <$> v .: 1
            <|> PostBodyRtf      <$> v .: 2


Given an enum,

enum Role {

The Pinchable instance for it will be,

data Role = RoleDisabled | RoleUser | RoleAdmin

instance Pinchable Role where
    type Tag Role = TEnum

    pinch RoleDisabled = pinch (0 :: Int32)
    pinch RoleUser     = pinch (1 :: Int32)
    pinch RoleAdmin    = pinch (2 :: Int32)

    unpinch v = do
       value <- unpinch v
       case (value :: Int32) of
           0 -> Right RoleDisabled
           1 -> Right RoleUser
           2 -> Right RoleAdmin
           _ -> Left $ "Unknown role: " ++ show value



(.=) :: Pinchable a => Int16 -> a -> FieldPair Source

Construct a FieldPair from a field identifier and a Pinchable value.

(?=) :: Pinchable a => Int16 -> Maybe a -> FieldPair Source

Construct a FieldPair from a field identifier and an optional Pinchable value.

struct :: [FieldPair] -> Value TStruct Source

Construct a Value tagged with a TStruct from the given key-value pairs. Optional fields whose values were omitted will be ignored.

struct [1 .= ("Hello" :: Text), 2 .= (42 :: Int16)]

union :: Pinchable a => Int16 -> a -> Value TUnion Source

Constructs a Value tagged with TUnion.

union 1 ("foo" :: ByteString)

type FieldPair = (Int16, Maybe SomeValue) Source

A pair of field identifier and maybe a value stored in the field. If the value is absent, the field will be ignored.


(.:) :: forall a. Pinchable a => Value TStruct -> Int16 -> Parser a Source

Given a field ID and a Value TStruct, get the value stored in the struct under that field ID. The lookup fails if the field is absent or if it's not the same type as expected by this call's context.

(.:?) :: forall a. Pinchable a => Value TStruct -> Int16 -> Parser (Maybe a) Source

Given a field ID and a Value TStruct, get the optional value stored in the struct under the given field ID. The value returned is Nothing if it was absent or the wrong type. The lookup fails only if the value retrieved fails to unpinch.


Value is an intermediate representation of Thrift payloads tagged with TType tags. Types that want to be serialized into/deserialized from Thrift payloads need only define a way to convert themselves to and from Value objects via Pinchable.

data Value a Source

Value maps directly to serialized representation of Thrift types. It contains about as much information as what gets sent over the wire. Value objects are tagged with different TType values to indicate the type of the value.

Typical usage will not involve accessing the constructors for this type. Pinchable must be used to construct Value objects or convert them back to original types.


Eq (Value a) 
Show (Value a) 
NFData (Value a) 
Hashable (Value a) 
IsTType a => Pinchable (Value a) 
type Tag (Value a) = a 

data SomeValue where Source

SomeValue holds any value, regardless of type. This may be used when the type of the value is not necessarily known at compile time. Typically, this will be pattern matched on and code that depends on the value's TType will go inside the scope of the match.


SomeValue :: IsTType a => !(Value a) -> SomeValue 


data Message Source

Message envelope for Thrift payloads.

mkMessage Source


:: (Pinchable a, Tag a ~ TStruct) 
=> Text

Name of the target method.

-> MessageType

Type of the message.

-> Int32

Message ID.

-> a

Message payload. This must be an object which serializes into a struct.

-> Message 

Build a Message.

messageName :: Message -> Text Source

Name of the method to which this message is targeted.

messageType :: Message -> MessageType Source

Type of the message.

messageId :: Message -> Int32 Source

Sequence ID of the message.

If the clients expect to receive out-of-order responses, they may use the message ID to map responses back to their corresponding requests. If the client does not expect out-of-order responses, they are free to use the same message ID for all messages.

The server's contract regarding message IDs is that all responses must have the same message ID as their corresponding requests.

getMessageBody :: (Pinchable a, Tag a ~ TStruct) => Message -> Either String a Source

Read the message contents.

This returns a Left result if the message contents do not match the requested type.

data MessageType Source

Type of message being sent.



A call to a specific method.

The message body is the request arguments struct.


Response to a call.

The message body is the response union.


Failure to make a call.

Note: This message type is not used for exceptions that are defined under the throws clause of a method. Those exceptions are part of the response union of the method and are received in a Reply. This message type is used for Thrift-level failures.


One-way call that expects no response.


data Protocol Source

Protocols define a specific way to convert values into binary and back.

binaryProtocol :: Protocol Source

Provides an implementation of the Thrift Binary Protocol.

compactProtocol :: Protocol Source

Provides an implementation of the Thrift Compact Protocol.


TType is used to refer to the Thrift protocol-level type of a value.

data TType a Source

Represents the type of a Thrift value.

Objects of this type are tagged with one of the TType tags, so this type also acts as a singleton on the TTypes. It allows writing code that can enforce properties about the TType of values at compile time.


Eq (TType a) 
Show (TType a) 
Hashable (TType a) 

class Typeable a => IsTType a where Source

Typeclass used to map type-leve TTypes into TType objects. All TType tags are instances of this class.


ttype :: TType a Source

Based on the context in which this is used, it will automatically return the corresponding TType object.


TType tags allow writing code that depends on knowing the TType of values, or asserting conditions on it, at compile time.

For example, values in a map, list, or set must all have the same TType. This is enforced at the type level by parameterizing Value over these tags.

data TBool Source



data TByte Source



data TDouble Source



type TEnum = TInt32 Source


data TInt16 Source



data TInt32 Source



data TInt64 Source



data TBinary Source



data TStruct Source



type TUnion = TStruct Source


type TException = TStruct Source


data TMap Source

map<k, v>


data TSet Source



data TList Source