Объявите, что все экземпляры класса типов находятся в другом классе типов без изменения исходных объявлений класса.
В пакете crypto-api есть API Crypto.Random, который определяет, что значит быть "генератором псевдослучайных чисел".
Я реализовал этот API, используя экземпляр класса RandomGen System.Random, а именно 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 ответа
Автор крипто-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
с (или в моем случае, Word32
s) закодировать их и вернуть байты.
Так как RandomGen
ограничивается только генерацией (и расщеплением), не существует прямого способа поддержать свойства инсталяции, реинстанциации или запроса, такие как длина семени.
Насколько я знаю, это невозможно, если только вы не хотите включить 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 и получил этот ответ (тот же обходной путь, который предложил Томас; я считаю это уродливым).