Skip to content

Bitwise-oriented semigroup and monoid newtype wrappers for Data.Bits.Bits instances

I've found myself needing these often, and given the existence of similar newtypes for directing (<>) and mempty for a range of other types, these are conspicuous by their absence. Additionally, while oneBits isn't technically necessary, it's a lot more concise than complement zeroBits, and I found myself needing it often.

To that end, I've sketched up this implementation. Technically speaking, GND isn't needed, but it means I don't have to repeat myself a lot. These could be made even more concise with coerce, but I decided not to do that, since that would require TypeApplications. I've limited derivation to methods that are specifically about bitwise operations, rather than stuff like Num.

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Data.Bits.Extra where

import           Data.Bits                      ( Bits(..)
                                                , FiniteBits(..)
                                                )

-- | Monoid under bitwise AND.
newtype Conj a = Conj { getConj :: a }
  deriving (Eq, Bounded, Enum, Bits, FiniteBits)

instance (Bits a) => Semigroup (Conj a) where
  (Conj x) <> (Conj y) = Conj (x .&. y)

instance (Bits a) => Monoid (Conj a) where
  mempty = Conj . complement $ zeroBits

-- | Monoid under bitwise OR.
newtype Disj a = Disj { getDisj :: a }
  deriving (Eq, Bounded, Enum, Bits, FiniteBits)

instance (Bits a) => Semigroup (Disj a) where
  (Disj x) <> (Disj y) = Disj (x .|. y)

instance (Bits a) => Monoid (Disj a) where
  mempty = Disj zeroBits

-- | Semigroup under bitwise XOR.
newtype Xor a = Xor { getXor :: a }
  deriving (Eq, Bounded, Enum, Bits, FiniteBits)

instance (Bits a) => Semigroup (Xor a) where
  (Xor x) <> (Xor y) = Xor (x `xor` y)

-- | Semigroup under bitwise \'equality\'; defined as '1' if the corresponding
-- bits match, '0' otherwise.
newtype Iff a = Iff { getIff :: a }
  deriving (Eq, Bounded, Enum, Bits, FiniteBits)

instance (Bits a) => Semigroup (Iff a) where
  (Iff x) <> (Iff y) = Iff . complement $ (x `xor` y)

-- not strictly necessary, but would be a big help
-- probably should be INLINE
oneBits :: (Bits a) => a
oneBits = complement zeroBits

Potentially this could include more, such as instances of Ord based on lexicographic ordering on the bits, rather than defaulting to the underlying one (such as the one for Int, which I believe is numeric). However, that's a separate issue. I also don't know if these belong in Data.Bits or Data.Semigroup (or Data.Monoid I guess).

Trac metadata
Trac field Value
Version 8.6.2
Type FeatureRequest
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component libraries/base
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture

Update (17/01/2021)

Based on my IRC conversation with @carter and @bgamari, as well as the comments here and on the MR, I've decided that the design will be as follows:

  • The names for the newtypes will be And, Ior, Xor and Iff (the last subject to change if SIMD instruction names prove to be helpful, but that's unlikely).
  • They will live in Data.Bits.
  • To break the cyclic dep problem, the current Data.Bits will become GHC.Bits, re-exported from Data.Bits.
  • oneBits will also live in Data.Bits.
  • Show, Read instances for all of the above newtypes will be stock-derived.

I think this is the right choice in light of everything I've seen and heard.

Edited by Hécate Kleidukos
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information