我正在尝试使用随机数洗牌任何 a 的列表。我在这里问这个问题的原因是因为我已经做了一个函数,我无法弄清楚它为什么不起作用。
pick :: [a] -> IO a
pick xs = do
n <- randomRIO (0, length xs - 1)
return $ xs !! n
shuffle :: [a] -> [IO a]
shuffle ls = do
x <- pick ls
let y = remove x ls
(return x) : shuffle y
-- Remove an element from a list (Only first appearance)
remove :: (Eq a) => a -> [a] -> [a]
remove _ [] = []
remove r (x:xs) = if x == r then xs else x : remove r xs
我得到的错误:
num.hs:31:10: error:
* Couldn't match type `IO' with `[]'
Expected type: [a]
Actual type: IO a
* In a stmt of a 'do' block: x <- pick ls
In the expression:
do x <- pick ls
let y = remove x ls
(return x) : shuffle y
In an equation for `shuffle':
shuffle ls
= do x <- pick ls
let y = ...
(return x) : shuffle y
|
31 | x <- pick ls
| ^^^^^^^
对我来说没有意义的是它说它收到了一个类型 [a] 而不是 IO a for pick,但 ls 被定义为 [a]?
如果这有什么根本性的问题,我只是不明白,有没有另一种方法可以在 Haskell 中洗牌一个如此简单的列表?最好没有任何进口。
正在发生的事情是shuffle
的类型签名意味着其 do-block 具有类型[IO a]
。 这意味着这个 do-block 的 monad 不是您想要的IO
,而是列表[]
的 monad 实例,因为这是这里的"最外层"类型构造函数。 因此,对于某些类型t
,表达式pick ls
需要具有类型[t]
,但pick
的类型签名意味着pick ls
对于某些类型a
具有类型IO a
。 GHC抱怨说,它希望pick ls
有一个列表类型[a]
(因为do-block的类型(,但其实际类型是IO a
(因为pick
的类型签名(。
我相信你犯的概念错误是你认为IO
是类型上的一种修饰符,使其对IO友好。 因此,如果IO a
是可以使用有效的 IO 计算生成的a
,那么[IO a]
是可以使用有效的 IO 计算生成的a
的列表,这必须是真的。 但这是错误的!
相反,您应该将IO a
视为一个 IO操作(如配方(,在执行时可以产生a
。 如果你想要一个这样的a
的列表,你不想要一个动作/配方的列表,每个动作/配方都会产生一个a
(即,你不需要[IO a]
(。 相反,您需要一个生成a
列表的单个操作/配方,因此您需要一个IO [a]
。
因此,shuffle
应该具有类型签名:
shuffle :: [a] -> IO [a]
进行此更改将导致最后一个表达式出现另一个错误:
(return x) : shuffle y
这里的问题来自相同的概念错误:您正在采取(微不足道的(操作/配方来生成x
并尝试创建操作/配方列表(尽管现在shuffle y
不再是列表,因此存在类型不匹配(。 相反,您希望将其替换为:
xs <- shuffle y -- use `shuffle y :: IO [a]` action to get `xs :: [a]`
return (x:xs) -- make an action to return the whole list (type `IO [a]`)
您还会发现需要添加一个Eq a
约束来 shuffle,因为它需要调用remove
;此外,除非您正确处理空列表情况,否则这将挂起。shuffle
的最终版本将是:
shuffle :: (Eq a) => [a] -> IO [a]
shuffle [] = return []
shuffle ls = do
x <- pick ls
let y = remove x ls
xs <- shuffle y
return (x:xs)
这应该有效:
> shuffle [1..10]
[6,8,7,2,5,10,1,9,4,3]
您可能正在寻找如下函数:
shuffle :: [a] ->IO[a]
shuffle [] = return []
shuffle ls = do
x <- pick ls
fmap (x:) (shuffle (remove x ls))
因此,您首先从ls
中选择一个元素,然后在列表列表中递归。然后我们可以返回一个列表(x:xs)
.
以上可以更加优雅。我把这当作一个练习。例如,每次迭代计算列表的length
通常不是一个好主意,因为这会使算法O(n2(。此外,您可能希望将pick
重写为在删除后返回项目和列表的函数。