在函数中使用时,在Haskell中返回随机类型

时间:2014-01-26 19:10:04

标签: haskell random types

假设:

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'

2 个答案:

答案 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,这会强制rFloat

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的地方。