Declare all instances of a typeclass are in another typeclass without modifying the original class declarations

后端 未结 2 730
日久生厌
日久生厌 2021-01-23 08:39

There is an Crypto.Random API inside the crypto-api package that specifies what it means for something to be a \"pseudorandom number generator\".

I have implemented this

相关标签:
2条回答
  • 2021-01-23 08:55

    As far as I know, this is impossible, unless you're willing to turn on UndecidableInstances (which, of course, can make the typechecker go in an infinite loop). Here's an example that makes every instance of Monad an instance of Functor:

    {-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
    
    module Main
           where
    
    import Control.Monad (liftM)
    
    instance (Monad a) => Functor a where
        fmap = liftM
    
    
    -- Test code
    data MyState a = MyState { unM :: a }
                   deriving Show
    
    instance Monad MyState where
      return a = MyState a
      (>>=) m k = k (unM m)
    
    main :: IO ()
    main = print . fmap (+ 1) . MyState $ 1
    

    Testing:

    *Main> :main
    MyState { unM = 2 }
    

    In your case, this translates to:

    {-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
    
    instance (RandomGen a) => CryptoRandomGen a where
      newGen = ...
      genSeedLength = ...
      genBytes = ...
      reseed = ...
    

    As an aside, I once asked how to implement this without UndecidableInstances on haskell-cafe and got this answer (the same workaround that Thomas proposed; I consider it ugly).

    0 讨论(0)
  • 2021-01-23 09:14

    Crypto-API author here. Please don't do this - it's really a violation of the implicit properties of CryptoRandomGen.

    That said, here's how I'd do it: Just make a newtype that wraps your RandomGen and make that newtype an instance of CryptoRandomGen.

    newtype AsCRG g = ACRG { unACRG :: g}
    
    instance RandomGen g => CryptoRandomGen (AsCRG g) where
        newGen = -- This is not possible to implement with only a 'RandomGen' constraint.  Perhaps you want a 'Default' instance too?
        genSeedLength = -- This is also not possible from just 'RandomGen'
        genBytes nr g =
            let (g1,g2) = split g
                randInts :: [Word32]
                randInts = B.concat . map Data.Serialize.encode
                         . take ((nr + 3) `div` 4)
                         $ (randoms g1 :: [Word32])
            in (B.take nr randInts, g2)
        reseed _ _ = -- not possible w/o more constraints
        newGenIO = -- not possible w/o more constraints
    

    So you see, you can split the generator (or manage many intermediate generators), make the right number of Ints (or in my case, Word32s), encode them, and return the bytes.

    Because RandomGen is limited to just generation (and splitting), there isn't any straight-forward way to support instatiation, reinstantiation, or querying properties such as the seed length.

    0 讨论(0)
提交回复
热议问题