Объявите все экземпляры класса типов в другом классе типов без изменения исходных объявлений класса - PullRequest
1 голос
/ 11 декабря 2011

В пакете 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 ]

5 голосов
/ 11 декабря 2011

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

Поскольку RandomGen ограничивается только генерацией (и разбиением), не существует какого-либо прямого способа поддержки таких свойств, как длина семени.

1 голос
/ 11 декабря 2011

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

...