Haskell中的IO Int和Int

4

我最近开始学习Haskell。我正在尝试编写一个程序,从数组中随机选择一个元素:

import System.Random

randomInt :: (Int, Int) -> IO Int
randomInt range = randomRIO range :: IO Int

choseRandom :: [a] -> a
choseRandom list = 
    length list
    >>=
        (\l -> randomInt(0,l-1))
        >>=
            (\num -> (list !! num))

main :: IO ()
main = undefined

我遇到了以下错误:

Build FAILED

C:\Users\User\Haskell\Real\src\Main.hs: line 7, column 9:
  Couldn't match expected type `IO Int' with actual type `Int'

    In the return type of a call of `length'

    In the first argument of `(>>=)', namely `length list'

    In the first argument of `(>>=)', namely

      `length list >>= (\ l -> randomInt (0, l - 1))'

我做错了什么?对于初学者来说,处理单子很困难。
5个回答

7

由于您在 choseRandom 函数中使用了 IO,因此需要更改类型签名:

choseRandom :: [a] -> IO a

其次,您不需要使用>>=来获取列表的长度。 >>= 的类型为:
Monad m => m a -> (a -> m b) -> m b

length的类型是[a] -> Int,所以length list的类型是Int,它不是一个单子。

你可以在调用randomInt时直接计算它:

choseRandom :: [a] -> IO a
choseRandom list = 
    randomInt(0, length list) >>= (\num -> return (list !! num))

这与

choseRandom :: [a] -> IO a
choseRandom list = fmap (\num -> (list !! num)) (randomInt(0, length list))

3
一旦他掌握了 fmap 的概念,丑陋的 (\num -> (list !! num)) 就可以简写为 (list !!) - Ingo

7

第一次处理单子对我来说很难

是的,如果避免语法支持会让它变得更加困难。只需按照以下方式编写:

choseRandom list = do
   let l = length list
   num <- randomInt(0,l-1)
   return (list !! num)

这看起来不是更好了吗?

现在进入正题:根据其类型指示,randomRIO函数使用某些全局状态(可能是系统计时器)。因此,您只能在IO Monad中使用RandomRIO的结果。

另一种选择是在main函数中初始化一个随机生成器,并将该生成器传递给需要“随机”值的纯函数。


这不是系统计时器,而是一个共享的可变变量,其内容类型为StdGen,这与库中的纯函数显式传递的生成器类型相同。基于IO的随机数函数本质上只是在纯函数周围包装读写该变量。 - Ørjan Johansen

4

这应该可以工作:

import System.Random

randomInt :: (Int, Int) -> IO Int
randomInt range = randomRIO range :: IO Int

choseRandom :: [b] -> IO b
choseRandom list = randomInt (0, length list) >>= \x -> return $ list !! x

我认为这种写法更加惯用:

choseRandom list = do
 a <- randomInt (0, length list)
 return $ list !! a

你的 choseRandom 函数存在问题,其类型标识有误。正确的类型应该为 m a -> (a -> m b) -> m b,因此你需要使用 return 将结果重新包装到单子中。
因此它的操作方式如下: randomInt(0, length list) 将会给出 IO Int,通过使用 >>= 函数从中提取出 Int 值。现在可以使用 !! 函数从列表中提取出相应的元素。但由于输出类型应为 m b,因此你应该使用 return 将其重新包装到单子中。

3
首先,您需要修复choseRandom的类型签名。其次,如果您使用do符号,我认为您会发现这更容易。
choseRandom :: [a] -> IO a
choseRandom list = do
  let l = length list
  num <- randomInt (0, l-1)
  return (list !! num)

对于正在学习单子的人,我建议一开始避免使用 do 符号。 - Simon Bergot

0
choseRandom :: [a] -> IO a
choseRandom xs = (xs !!) <$> randomRIO (0, length xs - 1)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接