Repository navigation
does not work: newtype D = D Double deriving UniformRange #185
Description
Activity
It is a known limitation, which is why we also do not using deriving mechanism for
newtypewrappers inrandom, eg:random/src/System/Random/Internal.hs
Lines 1297 to 1300 in 8ee71aa
instance UniformRange CDouble where uniformRM (CDouble l, CDouble h) = fmap CDouble . uniformRM (l, h) {-# INLINE uniformRM #-} isInRange = isInRangeOrd The problem is that you cannot use
GeneralizedNewtypeDerivingto derive a type class instance that has a function with a nominal role, which polymorphic types have, since they are unknownLet me provide a slightly simpler example. Let's say we have a function that converts
InttoWordsafely:int2word :: Int -> Either String Word int2word i = if i < 0 then Left "Word can't be negative" else Right $ fromIntegral i
And we also have a general interface like this that can convert
Intto other types safely and an instance that uses above implementation:class PolyInt a where polyInt :: MonadFail m => Int -> m a instance PolyInt Word where polyInt = either fail pure . int2word
Because result of
polyInthasm a, it is impossible for the compiler to provide an instance usingGeneralizedNewtypeDerivingthat uses coercion underneath, becauseminm ais not known to be coercible. Hence if we try to make a derived instance:newtype MyWord = MyWord Word deriving PolyInt
we'll get a type checker error, that essentially says the same thing as I did in more concise way:
• Couldn't match representation of type: m Word with that of: m MyWord arising from the coercion of the method ‘polyInt’ from type ‘forall (m :: * -> *). MonadFail m => Int -> m Word’ to type ‘forall (m :: * -> *). MonadFail m => Int -> m MyWord’ NB: We cannot know what roles the parameters to ‘m’ have; we must assume that the role is nominal • When deriving the instance for (PolyInt MyWord)
In this particular example we could work around this limitation, by making the type class more stringent:
class MonoInt a where monoInt :: Int -> Either String a polyInt' :: (MonadFail m, MonoInt a) => Int -> m a polyInt' = either fail pure . monoInt instance MonoInt Word where monoInt = int2word newtype MyWord = MyWord Word deriving (MonoInt)
We can do this because
Either Stringcaptures the essence ofMonadFail.Unfortunately, I do not see a way to do something like that for
StatefulGen, since we actually rely on different behavior of themfor producing random numbers.That being said, I don't think this is a severe limitation of the interface, considering the generality we get from stateful generators.
Here is another example of a similar problem from ghc issue tracker that goes into some more explanation, if you want to learn more about it: https://gitlab.haskell.org/ghc/ghc/-/issues/21957
Thanks, I see, that seems to be a shortcoming of the type system (missing "higher order roles").
Perhaps it is possible to add to the docs a recommended way of writing instances (for newtypes). I see that there is such a hint for
Finitealready. By copying that, I getghci> newtype B = B Bool deriving (Show, Generic, Random, Uniform, UniformRange) ghci> random @B @StdGen <$> getStdGen (B True,StdGen {unStdGen = SMGen 6891688420623091382 2170343918981675385})OK, works. This does not:
ghci> newtype D = D Double deriving (Show, Generic, Random, Uniform, UniformRange) <interactive>:12:55: error: [GHC-39999] • Could not deduce ‘Uniform Double’OK, that's because
Randomis considered legacy (? - the default impl. requiresUniformwhichDoubledoes not have, with good reason). This works:ghci> newtype D = D Double deriving (Show, Generic, UniformRange) ghci> uniformR @D @StdGen (D 0, D 1) <$> getStdGenSo that solves my immediate problem.
Still, documentation could be improved (I can make a separate issue)
- "introduction" section does mention
RandomGen, should also mentionUniform - "usage" section shows a method from
Uniform, should also mention the class - later section "how to implement RandomGen" is fine, should also have "how to implement Uniform" somewhere (as that would be the much more frequent use case?)
- "introduction" section does mention
I have a newtype over Double, I want to use the built-in Double generator, but I get this error.
I can write the instance just fine
but that's precisely what I wanted to avoid. It looks completely generic so the compiler should be able to provide/derive it?