1

我有一种情况,我有一些类似的东西newtypes都需要是Random,Arbitrary和许多其他东西的实例。它们都需要函数、、、等的相同自定义实现。所以我将所有这些实现放在一个类中。randomRrandomarbitrary

这是一个简化的示例,仅处理Random.

{-# LANGUAGE ConstrainedClassMethods #-}
{-# LANGUAGE FlexibleContexts        #-}
{-# LANGUAGE ScopedTypeVariables     #-}
{-# LANGUAGE StandaloneDeriving      #-}
{-# LANGUAGE TypeFamilies            #-}

import qualified System.Random as SR

-- Numbers that are restricted to a narrower range
class Narrow t where
  type BaseType t

  -- Unsafe constructor for the instance type
  bless :: BaseType t -> t

  -- Safe constructor for the instance type
  narrow :: (Ord t, Bounded t) => BaseType t -> t
  narrow x | x' < (minBound :: t) = error "too small"
           | x' > (maxBound :: t) = error "too big"
           | otherwise     = x'
    where x' = bless x :: t

  -- Deconstructor for the instance type
  wide :: t -> BaseType t

  -- Random
  randomR
    :: (Ord t, Bounded t, SR.Random (BaseType t), SR.RandomGen g)
    => (t, t) -> g -> (t, g)
  randomR (a, b) g = (narrow x, g')
    where (x, g') = SR.randomR (wide a, wide b) g

  random
    :: (Ord t, Bounded t, SR.Random t, SR.RandomGen g)
    => g -> (t, g)
  random = SR.randomR (minBound, maxBound)

这是我想要的其中一种类型的示例。

-- | A number on the unit interval
newtype UIDouble = UIDouble Double
  deriving (Eq, Ord)

instance Bounded UIDouble where
  minBound = UIDouble 0
  maxBound = UIDouble 1

instance Narrow UIDouble where
  type BaseType UIDouble = Double
  bless = UIDouble
  wide (UIDouble x) = x
    

我希望这是一个实例Random。理想情况下,我想写一些类似的东西:

deriving ?strategy? instance SR.Random UIDouble

并让编译器知道使用中定义的方法Narrow来实现Random. 但相反,我必须写

instance SR.Random UIDouble where
  randomR = randomR
  random = random

对几种方法执行此操作不是问题,但是对我的每种类型执行Num, Fractional, RealFrac, Floating,Serialize等操作有点乏味。

我探索过的另一种方法是写

instance (Narrow t) => SR.Random t where
  randomR = randomR
  random = random

因为我只需要为班级写一次,而不是为每种类型重复一次。但这导致UndecidableInstances我理解是不好的。我可以做到这一点TemplateHaskell,我敢肯定。但我想知道是否有一些花哨的语言编译指示或类型级编程魔法可以简化这一点?

4

1 回答 1

4

首先,您定义一个新类型并一劳永逸地给它您想要的实例:

newtype UseNarrow a = UN a
instance Narrow a => SR.Random (UseNarrow a) where
    randomR (UN lo, UN hi) g = (UN v, g) where v = randomR (lo, hi) g
    random g = (UN v, g) where v = random g

然后在你想使用该实例的所有地方,你写:

deriving via (UseNarrow UIDouble) instance SR.Random UIDouble

由于我没有测试上述内容,因此我可能有一些语法错误。但是你应该有这个想法。

如需进一步阅读,请DerivingVia在 GHC 用户手册中查找。

于 2021-09-27T18:21:40.767 回答