Commit a1cd40db authored by Roman S. Borschel's avatar Roman S. Borschel 🐌

Merge branch 'release/4.0.0'

parents c2b6fa9a 0ca29607
Pipeline #21416491 failed with stage
in 12 minutes and 59 seconds
variables:
STACK_ROOT: "${CI_PROJECT_DIR}/.stack"
cache:
key: "$CI_JOB_NAME"
paths:
- .stack
before_script:
- apt-get update
- apt-get install -y libstdc++-4.9-dev g++
- cabal update
- apt -qq update
- apt -qq install xz-utils make libstdc++-4.9-dev g++ > /dev/null
- stack upgrade && hash -d stack && stack --version
test:8.0:
image: haskell:8.0
test:8.2:
image: haskell:8.2
script:
- cabal install --enable-test --enable-bench --only-dep -j
- cabal build
- cabal test
- cabal bench
- stack -j 1 test --fast
test:7.10:
image: haskell:7.10
test:8.0:
image: haskell:8.0
script:
- cabal install --enable-test --enable-bench --only-dep -j
- cabal build
- cabal test
- cabal bench
- stack -j 1 --resolver lts-9.21 test --fast
test:7.8:
image: haskell:7.8
script:
- cabal install --enable-test --enable-bench --only-dep -j
- cabal build
- cabal test
- cabal bench
......@@ -2,12 +2,16 @@ Maintainers
-----------
- Toralf Wittner <tw@dtex.org>
- Roman S. Borschel <roman@pkaboo.org>
Original Authors
----------------
- Toralf Wittner <tw@dtex.org>
- Roman S. Borschel <roman.borschel@googlemail.com>
- Roman S. Borschel <roman@pkaboo.org>
Contributors
------------
- Ewout Van Troostenberghe <e@ewout.name>
- Steve Severance <sseverance@alphaheavy.com>
4.0.0
-----
- Add support for CQL V4 binary protocol.
- Remove support for CQL V2 binary protocol.
- Documentation updates.
- Bugfix: The generic 'Row' type did not account for null values
upon decoding.
- Add a minimal stack.yaml for stack builds.
3.1.1
-----
- Fix compatibility with template-haskell 2.11.0.0
......
......@@ -2,7 +2,7 @@ CQL Binary Protocol Implementation
==================================
This Haskell library implements Cassandra's CQL Binary Protocol versions
[2] and [3]. It provides encoding and decoding functionality as well
[3] and [4]. It provides encoding and decoding functionality as well
as representations of the various protocol related types.
Contributing
......@@ -11,5 +11,5 @@ Contributing
If you want to contribute to this project please read the file
CONTRIBUTING first.
[2]: https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v2.spec
[3]: https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v3.spec
[4]: https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec
name: cql
version: 3.1.1
version: 4.0.0
synopsis: Cassandra CQL binary protocol.
stability: experimental
license: OtherLicense
license-file: LICENSE
author: Toralf Wittner, Roman S. Borschel
maintainer: Toralf Wittner <tw@dtex.org>
maintainer: Toralf Wittner <tw@dtex.org>,
Roman S. Borschel <roman@pkaboo.org>
copyright: (C) 2014-2015 Toralf Wittner, Roman S. Borschel
homepage: https://gitlab.com/twittner/cql/
bug-reports: https://gitlab.com/twittner/cql/issues
......@@ -19,9 +20,9 @@ extra-source-files: README.md
description:
Implementation of Cassandra's CQL Binary Protocol
<https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v2.spec Version 2>
<https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v3.spec Version 3>
and
<https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v3.spec Version 3>.
<https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec Version 4>.
.
It provides encoding and decoding functionality as well as representations
of the various protocol related types.
......@@ -64,6 +65,7 @@ library
base >= 4.5 && < 5.0
, bytestring >= 0.10
, cereal >= 0.3
, containers >= 0.5
, Decimal >= 0.3
, iproute >= 1.3
, network >= 2.4
......
......@@ -105,6 +105,8 @@ module Database.CQL.Protocol
-- * Response
, Response (..)
, warnings
, traceId
, unpack
-- ** Ready
......
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Database.CQL.Protocol.Class (Cql (..)) where
......@@ -15,6 +16,8 @@ import Data.UUID (UUID)
import Database.CQL.Protocol.Types
import Prelude
import qualified Database.CQL.Protocol.Tuple.TH as Tuples
-- | A type that can be converted from and to some CQL 'Value'.
--
-- This type-class is used to map custom types to Cassandra data-types.
......@@ -46,6 +49,24 @@ instance Cql Bool where
fromCql (CqlBoolean b) = Right b
fromCql _ = Left "Expected CqlBoolean."
------------------------------------------------------------------------------
-- Int8
instance Cql Int8 where
ctype = Tagged TinyIntColumn
toCql = CqlTinyInt
fromCql (CqlTinyInt i) = Right i
fromCql _ = Left "Expected CqlTinyInt."
------------------------------------------------------------------------------
-- Int16
instance Cql Int16 where
ctype = Tagged SmallIntColumn
toCql = CqlSmallInt
fromCql (CqlSmallInt i) = Right i
fromCql _ = Left "Expected CqlSmallInt."
------------------------------------------------------------------------------
-- Int32
......@@ -222,3 +243,5 @@ instance Cql a => Cql (Set a) where
toCql (Set a) = CqlSet $ map toCql a
fromCql (CqlSet a) = Set <$> mapM fromCql a
fromCql _ = Left "Expected CqlSet."
Tuples.genCqlInstances 16
This diff is collapsed.
......@@ -20,7 +20,9 @@ module Database.CQL.Protocol.Header
-- ** Flags
, Flags
, compress
, customPayload
, tracing
, warning
, isSet
, encodeFlags
, decodeFlags
......@@ -83,12 +85,12 @@ header v = runGetLazy (decodeHeader v)
-- Version
mapVersion :: Version -> Word8
mapVersion V4 = 4
mapVersion V3 = 3
mapVersion V2 = 2
toVersion :: Word8 -> Get Version
toVersion 2 = return V2
toVersion 3 = return V3
toVersion 4 = return V4
toVersion w = fail $ "decode-version: unknown: " ++ show w
------------------------------------------------------------------------------
......@@ -120,12 +122,12 @@ fromStreamId :: StreamId -> Int
fromStreamId (StreamId i) = fromIntegral i
encodeStreamId :: Version -> Putter StreamId
encodeStreamId V4 (StreamId x) = encodeSignedShort (fromIntegral x)
encodeStreamId V3 (StreamId x) = encodeSignedShort (fromIntegral x)
encodeStreamId V2 (StreamId x) = encodeSignedByte (fromIntegral x)
decodeStreamId :: Version -> Get StreamId
decodeStreamId V4 = StreamId <$> decodeSignedShort
decodeStreamId V3 = StreamId <$> decodeSignedShort
decodeStreamId V2 = StreamId . fromIntegral <$> decodeSignedByte
------------------------------------------------------------------------------
-- Flags
......@@ -154,6 +156,12 @@ compress = Flags 1
tracing :: Flags
tracing = Flags 2
customPayload :: Flags
customPayload = Flags 4
warning :: Flags
warning = Flags 8
-- | Check if a particular flag is present.
isSet :: Flags -> Flags -> Bool
isSet (Flags a) (Flags b) = a .&. b == a
......@@ -251,9 +251,8 @@ encodeBatch v (Batch t q c s) = do
encodeShort (fromIntegral (length q))
mapM_ (encodeBatchQuery v) q
encodeConsistency c
when (v == V3) $ do
put batchFlags
traverse_ encodeConsistency (mapCons <$> s)
put batchFlags
traverse_ encodeConsistency (mapCons <$> s)
where
batchFlags :: Word8
batchFlags = if isJust s then 0x10 else 0x0
......@@ -293,18 +292,38 @@ encodeBatchQuery n (BatchPrepared (QueryId i) v) = do
-- | Query parameters.
data QueryParams a = QueryParams
{ consistency :: !Consistency -- ^ consistency leven to use
, skipMetaData :: !Bool -- ^ skip metadata in response
, values :: a -- ^ query arguments
, pageSize :: Maybe Int32 -- ^ desired result set size
, queryPagingState :: Maybe PagingState
{ consistency :: !Consistency
-- ^ (Regular) consistency level to use.
, skipMetaData :: !Bool
-- ^ Whether to omit the metadata in the 'Response'
-- of the query. This is an optimisation only relevant for
-- use with prepared queries, for which the metadata obtained
-- from the 'PreparedResult' may be reused.
, values :: a
-- ^ The bound parameters of the query.
, pageSize :: Maybe Int32
-- ^ The desired maximum result set size.
, queryPagingState :: Maybe PagingState
-- ^ The current paging state that determines the "offset"
-- of the results to return for a read query.
, serialConsistency :: Maybe SerialConsistency
-- ^ Serial consistency level to use for conditional updates
-- (aka "lightweight transactions"). Irrelevant for any other queries.
, enableTracing :: Maybe Bool
-- ^ Whether tracing should be enabled for the query, in which case the
-- 'Response' will carry a 'traceId'.
} deriving Show
-- | Consistency level for the serial phase of conditional updates.
-- | Consistency level for the serial phase of conditional updates
-- (aka "lightweight transactions").
--
-- See: <https://docs.datastax.com/en/cassandra/latest/cassandra/dml/dmlConfigSerialConsistency.html SerialConsistency>
data SerialConsistency
= SerialConsistency
-- ^ Default. Quorum-based linearizable consistency.
| LocalSerialConsistency
-- ^ Like 'SerialConsistency' except confined to a single (logical)
-- data center.
deriving Show
encodeQueryParams :: forall a. Tuple a => Version -> Putter (QueryParams a)
......
This diff is collapsed.
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
-- | A tuple represents the types of multiple cassandra columns. It is used
-- to check that column-types match.
......@@ -18,14 +17,106 @@ module Database.CQL.Protocol.Tuple
, rowLength
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Monad
import Data.Functor.Identity
import Data.Serialize
import Data.Vector (Vector, (!?))
import Data.Word
import Database.CQL.Protocol.Class
import Database.CQL.Protocol.Codec (putValue)
import Database.CQL.Protocol.Types
import Database.CQL.Protocol.Codec (putValue, getValue)
import Database.CQL.Protocol.Tuple.TH
import Database.CQL.Protocol.Types
import Prelude
import qualified Data.Vector as Vec
-- Row ----------------------------------------------------------------------
-- | A row is a vector of 'Value's.
data Row = Row
{ types :: ![ColumnType]
, values :: !(Vector Value)
} deriving (Eq, Show)
-- | Convert a row element.
fromRow :: Cql a => Int -> Row -> Either String a
fromRow i r =
case values r !? i of
Nothing -> Left "out of bounds access"
Just v -> fromCql v
mkRow :: [(Value, ColumnType)] -> Row
mkRow xs = let (v, t) = unzip xs in Row t (Vec.fromList v)
rowLength :: Row -> Int
rowLength r = Vec.length (values r)
columnTypes :: Row -> [ColumnType]
columnTypes = types
-- Tuples -------------------------------------------------------------------
-- Database.CQL.Protocol.Tuple does not export 'PrivateTuple' but only
-- 'Tuple' effectively turning 'Tuple' into a closed type-class.
class PrivateTuple a where
count :: Tagged a Int
check :: Tagged a ([ColumnType] -> [ColumnType])
tuple :: Version -> [ColumnType] -> Get a
store :: Version -> Putter a
class PrivateTuple a => Tuple a
-- Manual instances ---------------------------------------------------------
instance PrivateTuple () where
count = Tagged 0
check = Tagged $ const []
tuple _ _ = return ()
store _ = const $ return ()
instance Tuple ()
instance Cql a => PrivateTuple (Identity a) where
count = Tagged 1
check = Tagged $ typecheck [untag (ctype :: Tagged a ColumnType)]
tuple v _ = Identity <$> element v ctype
store v (Identity a) = do
put (1 :: Word16)
putValue v (toCql a)
instance Cql a => Tuple (Identity a)
instance PrivateTuple Row where
count = Tagged (-1)
check = Tagged $ const []
tuple v t = Row t . Vec.fromList <$> mapM (getValue v . MaybeColumn) t
store v r = do
put (fromIntegral (rowLength r) :: Word16)
Vec.mapM_ (putValue v) (values r)
instance Tuple Row
-- Implementation helpers ---------------------------------------------------
element :: Cql a => Version -> Tagged a ColumnType -> Get a
element v t = getValue v (untag t) >>= either fail return . fromCql
typecheck :: [ColumnType] -> [ColumnType] -> [ColumnType]
typecheck rr cc = if checkAll (===) rr cc then [] else rr
where
checkAll f as bs = and (zipWith f as bs)
checkField (a, b) (c, d) = a == c && b === d
TextColumn === VarCharColumn = True
VarCharColumn === TextColumn = True
(MaybeColumn a) === b = a === b
(ListColumn a) === (ListColumn b) = a === b
(SetColumn a) === (SetColumn b) = a === b
(MapColumn a b) === (MapColumn c d) = a === c && b === d
(UdtColumn a as) === (UdtColumn b bs) = a == b && checkAll checkField as bs
(TupleColumn as) === (TupleColumn bs) = checkAll (===) as bs
a === b = a == b
genInstances 48
......@@ -5,89 +5,10 @@ module Database.CQL.Protocol.Tuple.TH where
import Control.Applicative
import Control.Monad
import Data.Functor.Identity
import Data.Serialize
import Data.Vector (Vector, (!?))
import Data.Word
import Database.CQL.Protocol.Class
import Database.CQL.Protocol.Codec (putValue, getValue)
import Database.CQL.Protocol.Types
import Language.Haskell.TH
import Prelude
import qualified Data.Vector as Vec
------------------------------------------------------------------------------
-- Row
-- | A row is a vector of 'Value's.
data Row = Row
{ types :: !([ColumnType])
, values :: !(Vector Value)
} deriving (Eq, Show)
-- | Convert a row element.
fromRow :: Cql a => Int -> Row -> Either String a
fromRow i r =
case values r !? i of
Nothing -> Left "out of bounds access"
Just v -> fromCql v
mkRow :: [(Value, ColumnType)] -> Row
mkRow xs = let (v, t) = unzip xs in Row t (Vec.fromList v)
rowLength :: Row -> Int
rowLength r = Vec.length (values r)
columnTypes :: Row -> [ColumnType]
columnTypes = types
------------------------------------------------------------------------------
-- Tuples
-- Database.CQL.Protocol.Tuple does not export 'PrivateTuple' but only
-- 'Tuple' effectively turning 'Tuple' into a closed type-class.
class PrivateTuple a where
count :: Tagged a Int
check :: Tagged a ([ColumnType] -> [ColumnType])
tuple :: Version -> [ColumnType] -> Get a
store :: Version -> Putter a
class PrivateTuple a => Tuple a
------------------------------------------------------------------------------
-- Manual instances
instance PrivateTuple () where
count = Tagged 0
check = Tagged $ const []
tuple _ _ = return ()
store _ = const $ return ()
instance Tuple ()
instance Cql a => PrivateTuple (Identity a) where
count = Tagged 1
check = Tagged $ typecheck [untag (ctype :: Tagged a ColumnType)]
tuple v _ = Identity <$> element v ctype
store v (Identity a) = do
put (1 :: Word16)
putValue v (toCql a)
instance Cql a => Tuple (Identity a)
instance PrivateTuple Row where
count = Tagged (-1)
check = Tagged $ const []
tuple v t = Row t . Vec.fromList <$> mapM (getValue v) t
store v r = do
put (fromIntegral (rowLength r) :: Word16)
Vec.mapM_ (putValue v) (values r)
instance Tuple Row
------------------------------------------------------------------------------
-- Templated instances
-- Templated instances ------------------------------------------------------
genInstances :: Int -> Q [Dec]
genInstances n = join <$> mapM tupleInstance [2 .. n]
......@@ -112,7 +33,7 @@ tupleInstance n = do
[ InstanceD ctx (tcon "PrivateTuple" $: tupleType)
#endif
[ FunD (mkName "count") [countDecl n]
, FunD (mkName "check") [checkDecl vnames]
, FunD (mkName "check") [taggedDecl (var "typecheck") vnames]
, FunD (mkName "tuple") [td]
, FunD (mkName "store") [sd]
]
......@@ -128,15 +49,15 @@ countDecl n = Clause [] (NormalB body) []
where
body = con "Tagged" $$ litInt n
-- check = Tagged $
-- typecheck [ untag (ctype :: Tagged x ColumnType)
-- , untag (ctype :: Tagged y ColumnType)
-- , ...
-- ])
checkDecl :: [Name] -> Clause
checkDecl names = Clause [] (NormalB body) []
-- Tagged $ ident
-- [ untag (ctype :: Tagged x ColumnType)
-- , untag (ctype :: Tagged y ColumnType)
-- , ...
-- ])
taggedDecl :: Exp -> [Name] -> Clause
taggedDecl ident names = Clause [] (NormalB body) []
where
body = con "Tagged" $$ (var "typecheck" $$ ListE (map fn names))
body = con "Tagged" $$ (ident $$ ListE (map fn names))
fn n = var "untag" $$ SigE (var "ctype") (tty n)
tty n = tcon "Tagged" $: VarT n $: tcon "ColumnType"
......@@ -167,6 +88,73 @@ storeDecl n = do
size = var "put" $$ SigE (litInt n) (tcon "Word16")
value x v = var "putValue" $$ VarE x $$ (var "toCql" $$ VarE v)
genCqlInstances :: Int -> Q [Dec]
genCqlInstances n = join <$> mapM cqlInstances [2 .. n]
-- instance (Cql a, Cql b) => Cql (a, b) where
-- ctype = Tagged $ TupleColumn
-- [ untag (ctype :: Tagged a ColumnType)
-- , untag (ctype :: Tagged b ColumnType)
-- ]
-- toCql (a, b) = CqlTuple [toCql a, toCql b]
-- fromCql (CqlTuple [a, b]) = (,) <$> fromCql a <*> fromCql b
-- fromCql _ = Left "Expected CqlTuple with 2 elements."
cqlInstances :: Int -> Q [Dec]
cqlInstances n = do
let cql = mkName "Cql"
vnames <- replicateM n (newName "a")
let vtypes = map VarT vnames
let tupleType = foldl1 ($:) (TupleT n : vtypes)
#if MIN_VERSION_template_haskell(2,10,0)
let ctx = map (AppT (ConT cql)) vtypes
#else
let ctx = map (\t -> ClassP cql [t]) vtypes
#endif
tocql <- toCqlDecl
fromcql <- fromCqlDecl
return
#if MIN_VERSION_template_haskell(2,11,0)
[ InstanceD Nothing ctx (tcon "Cql" $: tupleType)
#else
[ InstanceD ctx (tcon "Cql" $: tupleType)
#endif
[ FunD (mkName "ctype") [taggedDecl (con "TupleColumn") vnames]
, FunD (mkName "toCql") [tocql]
, FunD (mkName "fromCql") [fromcql]
]
]
where
toCqlDecl = do
names <- replicateM n (newName "x")
let tocql nme = var "toCql" $$ VarE nme
return $ Clause
[TupP (map VarP names)]
(NormalB . AppE (con "CqlTuple") $ ListE $ map tocql names)
[]
fromCqlDecl = do
names <- replicateM n (newName "x")
Clause
[VarP (mkName "t")]
(NormalB $ CaseE (var "t")
[ Match (ParensP (ConP (mkName "CqlTuple") [ListP (map VarP names)]))
(NormalB $ body names)
[]
, Match WildP
(NormalB (con "Left" $$ failure))
[]
])
<$> combine
where
body names = UInfixE (var "combine") (var "<$>") (foldl1 star (fn names))
star a b = UInfixE a (var "<*>") b
fn names = map (AppE (var "fromCql") . VarE) names
combine = do
names <- replicateM n (newName "x")
let f = NormalB $ TupE (map VarE names)
return [ FunD (mkName "combine") [Clause (map VarP names) f []] ]
failure = LitE (StringL $ "Expected CqlTuple with " ++ show n ++ " elements")
------------------------------------------------------------------------------
-- Helpers
......@@ -186,26 +174,3 @@ tcon = ConT . mkName
($:) :: Type -> Type -> Type
($:) = AppT
------------------------------------------------------------------------------
-- Implementation helpers
element :: Cql a => Version -> Tagged a ColumnType -> Get a
element v t = getValue v (untag t) >>= either fail return . fromCql
typecheck :: [ColumnType] -> [ColumnType] -> [ColumnType]
typecheck rr cc = if checkAll (===) rr cc then [] else rr
where
checkAll f as bs = and (zipWith f as bs)
checkField (a, b) (c, d) = a == c && b === d
TextColumn === VarCharColumn = True
VarCharColumn === TextColumn = True
(MaybeColumn a) === b = a === b
(ListColumn a) === (ListColumn b) = a === b
(SetColumn a) === (SetColumn b) = a === b
(MapColumn a b) === (MapColumn c d) = a === c && b === d
(UdtColumn a as) === (UdtColumn b bs) = a == b && checkAll checkField as bs
(TupleColumn as) === (TupleColumn bs) = checkAll (===) as bs
a === b = a == b
......@@ -10,6 +10,8 @@ import Data.UUID (UUID)
import qualified Data.ByteString.Lazy as LB
import qualified Data.List as List
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import qualified Data.Text.Lazy as LT
newtype Keyspace = Keyspace
......@@ -34,9 +36,9 @@ instance IsString (QueryString k a b) where
-- | CQL binary protocol version.
data Version
= V2 -- ^ version 2
| V3 -- ^ version 3
deriving (Eq, Show)
= V3 -- ^ version 3
| V4 -- ^ version 4
deriving (Eq, Ord, Show)
-- | The CQL version (not the binary protocol version).
data CqlVersion
......@@ -63,18 +65,20 @@ noCompression :: Compression
noCompression = Compression None Just Just
-- | Consistency level.
--
-- See: <https://docs.datastax.com/en/cassandra/latest/cassandra/dml/dmlConfigConsistency.html Consistency>
data Consistency
= Any
| One
| LocalOne
| Two
| Three
| Quorum
| All
| LocalQuorum
| EachQuorum
| Serial
| LocalOne
| LocalSerial
| All
| EachQuorum -- ^ Only for write queries.
| Serial -- ^ Only for read queries.
| LocalSerial -- ^ Only for read queries.
deriving (Eq, Show)
-- | An opcode is a tag to distinguish protocol frame bodies.
......@@ -122,6 +126,10 @@ data ColumnType
| MapColumn !ColumnType !ColumnType
| TupleColumn [ColumnType]
| UdtColumn !Text [(Text, ColumnType)]
| DateColumn
| TimeColumn
| SmallIntColumn
| TinyIntColumn
deriving (Eq)
instance Show ColumnType where
......@@ -142,6 +150,10 @@ instance Show ColumnType where
show VarIntColumn = "varint"
show TimeUuidColumn = "timeuuid"
show InetColumn = "inet"
show DateColumn = "date"
show TimeColumn = "time"
show SmallIntColumn = "smallint"
show TinyIntColumn = "tinyint"
show (MaybeColumn a) = show a ++ "?"
show (ListColumn a) = showString "list<" . shows a . showString ">" $ ""
show (SetColumn a) = showString "set<" . shows a . showString ">" $ ""
......@@ -165,12 +177,18 @@ newtype Ascii = Ascii { fromAscii :: Text } deriving (Eq, Ord,
newtype Blob = Blob { fromBlob :: LB.ByteString } deriving (Eq, Ord, Show)
newtype Counter = Counter { fromCounter :: Int64 } deriving (Eq, Ord, Show)
newtype TimeUuid = TimeUuid { fromTimeUuid :: UUID } deriving (Eq, Ord, Show)
newtype Set a = Set { fromSet :: [a] } deriving (Show)
newtype Map a b = Map { fromMap :: [(a, b)] } deriving (Show)
newtype Set a = Set { fromSet :: [a] } deriving Show
newtype Map a b = Map { fromMap :: [(a, b)] } deriving Show
instance IsString Ascii where
fromString = Ascii . pack
instance (Eq a, Ord a) => Eq (Set a) where
a == b = Set.fromList (fromSet a) == Set.fromList (fromSet b)
instance (Eq k, Eq v, Ord k) => Eq (Map k v) where