声明类型类的所有实例都在另一个类型类中,而不修改原始类声明

时间:2011-12-11 00:30:34

标签: haskell instance subclass typeclass

crypto-api包中有一个Crypto.Random API,用于指定“伪随机数生成器”的含义。

我使用System.Random的RandomGen类实例实现了这个API,即StdGen:

instance CryptoRandomGen StdGen where
  newGen bs = Right $ mkStdGen $ shift e1 24 + shift e2 16 + shift e3 8 + e4
    where (e1 : e2 : e3 : e4 : _) = Prelude.map fromIntegral $ unpack bs
  genSeedLength = Tagged 4
  genBytes n g = Right $ genBytesHelper n empty g
    where genBytesHelper 0 partial gen = (partial, gen)
          genBytesHelper n partial gen = genBytesHelper (n-1) (partial `snoc` nextitem) newgen
            where (nextitem, newgen) = randomR (0, 255) gen
  reseed bs _ = newGen bs

但是,此实现仅适用于StdGen类型,但它确实适用于System.Random的RandomGen类型类中的任何内容。

有没有办法说使用给定的填充函数,RandomGen中的所有内容都是CryptoRandomGen的成员?我希望能够在我自己的代码中执行此操作,而无需更改这两个库中的任何一个的源代码。我的直觉是将第一行改为

instance (RandomGen a) => CryptoRandomGen a where

但这似乎在语法上不正确。

2 个答案:

答案 0 :(得分:5)

此处为Crypto-API作者。请不要这样做 - 这实际上违反了CryptoRandomGen的隐式属性。

那就是说,我就是这样做的:只需创建一个包裹RandomGen的新类型,并将该新类型设为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

所以你看,你可以分割生成器(或管理许多中间生成器),正确数量Int s(或者在我的情况下,Word32 s),编码它们,然后返回字节。

因为RandomGen仅限于生成(和拆分),所以没有任何直接的方法来支持实例化,重新实例化或查询种子长度等属性。

答案 1 :(得分:1)

据我所知,这是不可能的,除非你愿意打开UndecidableInstances(当然,这可以让类型检查器进入无限循环)。这是一个示例,它使Monad的每个实例都成为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

测试:

*Main> :main
MyState { unM = 2 }

在您的情况下,这转换为:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

instance (RandomGen a) => CryptoRandomGen a where
  newGen = ...
  genSeedLength = ...
  genBytes = ...
  reseed = ...

顺便说一下,我曾经问过如何在没有UndecidableInstances的情况下在haskell-cafe上实现这一点并获得this answer(与托马斯提出的相同的解决方法;我认为这很丑陋)。