On Fri, Jul 27, 2012 at 3:42 PM, Ian Lynagh <igloo@earth.li> wrote:
On Fri, Jul 27, 2012 at 03:06:04PM -0400, Edward Kmett wrote:
> There is currently no way to know whether or not calling
> Data.Bits.bitSizewill crash your program.
>
> I propose extending the Bits class to include:
>
> hasBitSize :: Bits b => b -> Bool
>
> such that it returns False for Integer and True for the other instances

Can you give an example of a situation in which you would use
hasBitSize, and do something useful if it returned False?

The following can only work if bitSize is well defined.

traverseBits :: (Applicative f, Bits b) => (Bool -> f Bool) -> b -> f b 
traverseBits f b = snd . Prelude.foldr step (bitSize b - 1,0) <$> traverse (f . testBit b) [0 .. bitSize b - 1] where
  step True (n,r) = (n - 1, setBit r n)
  step _    (n,r) = (n - 1, r)
 
to work around this I've had to use:

traverseBits :: (Applicative f, Bits b) => (Bool -> f Bool) -> b -> f b 
traverseBits f b = Prelude.foldr step 0 <$> traverse g bits
  where
    g n      = (,) n <$> f (testBit b n)
    bits     = Prelude.takeWhile hasBit [0..]
    hasBit n = complementBit b n /= b -- test to make sure that complementing this bit actually changes the value
    step (n,True) r = setBit r n
    step _        r = r

where I'm manually probing each bit to see that changing it changes the value.

Used with the other combinators in lens the former just crashes when you use

foldMapOf :: ((c -> Const m d) -> a -> Const m b) -> (c -> m) -> a -> m
foldMapOf l f = getConst . l (Const . f)

toListOf :: ((c -> Const [c] d) -> a -> Const [c] b) -> a -> [c]
toListOf l = foldMapOf l return

Now when we use it with an explicit signature both of these can do reasonable things:

ghci> toListOf traverseBits (5 :: Int)
[True,False,True,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]

But with defaulting choosing Integer
ghci> toListOf traverseBits 5

the former instance will crash, whereas the latter properly returns an infinite lazy list

[True,False,True,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,...

It would be much nicer not to have to probe using complementBit creating ever larger temporary integers just to see if the list of valid bit positions hadn't been exhausted.

Then I could test at the start to know whether I should use [0..] or [0..bitSize-1] and get away with much less code and much less unnecessary memory allocation.

-Edward

Would it be better to move bitSize into a separate class?

I don't think so. Ultimately, you should be able to know for every numeric type if it has a fixed or variable number of bits. In a perfect world 'd rather just have bitSize return a Maybe that way we don't have to truck with partial functions in the API.  

However, the case for just adding hasBitSize is that it avoids breaking existing code.
 
> since the vast majority of instances are finite, it may be reasonable to
> set the default definition to
>
> hasBitSize _ = False

Did you mean True?
 
Yes
 
Either way, I think I would personally prefer not to have a default, so
that people need to actually check existing instances.

Also perfectly reasonable. 

-Edward