@hackage bitfield0.0.0.1

Generic and easy to use haskell bitfields

bitfield

CI badge made with Haskell Hackage

Description

Generic and easy to use bitfields. Pack and unpack records into compact representations.

Example

import Data.Bitfield

data Example = Example { one :: Word8, two :: Bool, three :: Bool } deriving (Show, Generic)
 
x :: Bitfield Word16 Example
x = pack $ Example 5 False True

>>> x
"Example { one = 5, two = False, three = True }"
>>> get @"two" x
"False"
-- Requires OverloadedRecordDot
>>> x.two
"False"
>>> set @"three" x False
"Example { one = 5, two = False, three = False }"

x is represented using Word16 instead of the full heap object Example and thus takes far less memory.

Custom fields

There are two important typeclasses for working with types in bitfields: AsRep and HasFixedBitSize.

The most common way to use them is to derive them via either an underlying Enum or Integral instance.

Via Enum

data AEnum = A1 | A2 | A3
  deriving stock (Generic, Enum)
  deriving (HasFixedBitSize, AsRep rep) via (GenericEnum AEnum)

This still requires a Generic instance to count the constructors of AEnum for the HasFixedBitSize instance. The resulting field has a size of Log2 NumConstructors rounded up (2 bits for AEnum). The actual value will be constructed using the Enum instance, so unless that is derived, the constructor order won't matter.

Via Integral

newtype SmallInt = UnsafeSmallInt Int
  deriving (HasFixedBitSize, AsRep rep) via (ViaIntegral 5 Int)

This creates a HasFixedBitSize instance with a fixed bit size of 5 and a AsRep instance which reads and writes 5 bit values.

Note: The value is not truncated nor is it otherwise checked that the underlying value actually fits into the specified bit size. If it is too large it will write over other values!

Type safety

Any operation on a Bitfield checks (on the type level) if that Bitfield is valid. Any field operation also requires a HasField instance, guaranteeing that such a field exists. The following examples will not compile:

data Bad1 = Bad1 { a :: Bool, b :: Word8, c :: Bool }

x :: Bitfield Word8 Bad1
x = pack $ Bad1 True 1 True
-- Datatype Bad1 needs 10 bits, but the given representation Word8 has 8

y :: Bitfield Word16 Bad1 -> Word8
y b = get @"test" b
-- No instance HasField "test" Bad1 Word8