Объявите, что все экземпляры класса типов находятся в другом классе типов без изменения исходных объявлений класса.

В пакете 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с (или в моем случае, Word32s) закодировать их и вернуть байты.

Так как 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 и получил этот ответ (тот же обходной путь, который предложил Томас; я считаю это уродливым).

Другие вопросы по тегам