假设:
import System.Random
data Rarity = Common | Uncommon | Rare | Legendary
deriving (Show, Eq, Ord, Enum, Bounded)
我想要实例Random
,所以我开始使用类似的东西:
randomRarity :: (RandomGen g) => g -> (Rarity, g)
randomRarity g = case random g of
(r,g') | r < 0.50 -> (Common, g')
| r < 0.75 -> (Uncommon, g')
| r < 0.88 -> (Rare, g')
| otherwise -> (Legendary, g')
但这失败了,因为即使它知道它需要从所有比较中随机Ord
,它也无法告诉我真正想要的是随机Float
。所以我想我需要输入random g
的返回值,但我遇到了一个问题:
(random g :: (Float, ???))
因为它是一个元组,我将什么声明为第二类?我试过类似(random g :: (Float, RandomGen t))
的内容,但t
无法推断,我不知道如何将其与g
的类型相匹配。我使用StdGen
到处而不是RandomGen g
让它工作,但后来我无法实例Random
,它可能应该与任何随机RandomGen
一起工作,无论如何。并且,说实话,我甚至不关心它是什么,因为我只是传递它,但感觉就像我被迫。我试着通过以下方式找出正确的类型:
randomRarity g@(RandomGen t) = case (random g :: (Float, RandomGen t)) of
...
但是它运行在类型构造函数(私有的,不能少),而不是类型,所以我认为这是一种根本错误的方法。
在推理之后,最终对我有用的事情如下:
randomRarity g = case random g of
(r,g') | r' < 0.50 -> (Common, g')
| r' < 0.75 -> (Uncommon, g')
| r' < 0.88 -> (Rare, g')
| otherwise -> (Legendary, g')
where r' = r :: Float
这是相当简洁的,但是它声明了另一个远离它想要影响的东西的变量,这意味着当你看到r'
然后去看时你必须做一个双重考虑出来是什么,然后回来。最糟糕的是,它让我的好奇心不满意。所以我的问题是:
在这个上下文中是否有一种方法告诉random g
在我调用它时通过正确地声明元组中的第二个类型来生成一个Float,或者以某种方式从g
推断它。或者,如果不这样做,是否有办法约束r
的类型而不声明另一个变量,如r'
?
答案 0 :(得分:5)
如果您使用的是GHC,则可以使用-XScopedTypeVariables
(或将{-# LANGUAGE ScopedTypeVariables #-}
添加到文件顶部)并使用代码
randomRarity g = case random g of
(r::Float,g') | r < 0.50 -> (Common, g')
| r < 0.75 -> (Uncommon, g')
| r < 0.88 -> (Rare, g')
| otherwise -> (Legendary, g')
或者如果您想使用标准Haskell,可以使用
randomRarity g = let (r,g') = random g
in case r :: Float of
r | r < 0.50 -> (Common, g')
| r < 0.75 -> (Uncommon, g')
| r < 0.88 -> (Rare, g')
| otherwise -> (Legendary, g')
答案 1 :(得分:3)
我认为在这种情况下,最简单的解决方案是明确键入您要比较的数字之一。这会强制(<)
操作专门针对Float
,这会强制r
为Float
:
randomRarity :: (RandomGen g) => g -> (Rarity, g)
randomRarity g = case random g of
(r,g') | r < (0.50 :: Float) -> (Common, g')
| r < 0.75 -> (Uncommon, g')
| r < 0.88 -> (Rare, g')
| otherwise -> (Legendary, g')
否则,这是Haskell中的已知问题,标准解决方案是使用asTypeOf :: a -> a -> a
。它只是const
的类型限制版本,它限制了类型。因此,在这种情况下,如果我们无法知道random g :: (Float, ???)
的确切类型,我们可以从Control.Arrow
导入first
并映射该对的第一个元素,例如: / p>
randomRarity g = case first (`asTypeOf` (0 :: Float)) $ random g of
...
特别是,添加的表达式是
类型first (`asTypeOf` (0 :: Float)) :: (Float, a) -> (Float, a)
它不是很简洁,但它可以工作并且本地化到使用random g
的地方。