【问题标题】:Passing a randomly generated list as a parameter in Haskell在 Haskell 中将随机生成的列表作为参数传递
【发布时间】:2018-05-22 16:26:52
【问题描述】:

我是 Haskell 的新手,在整个 IO 方面确实遇到了麻烦。

我试图找出在haskell中遍历一个列表需要多长时间。我想生成一个随机数列表并将其作为参数传递给函数,以便我可以打印列表中的每个元素。我正在使用 CRITERION 包进行基准测试。代码如下:

{-# LANGUAGE OverloadedStrings #-}
import System.Random
import Control.Exception
import Criterion.Main

printElements [] = return ()
printElements (x:xs) = do print(x)
                          printElements xs

randomList 0 = return []
randomList n = do
  x  <- randomRIO (1,100)
  xs <- randomList (n-1)
  return (x:xs)


main = defaultMain [
  bgroup "printElements" [ bench "[1,2,3]"  $ whnf printElements (randomList 10)
               , bench "[4,5,6]"  $ whnf printElements [4,5,6,4,2,5]
               , bench "[7,8,9]"  $ whnf printElements [7,8,9,2,3,4]
               , bench "[10,11,12]" $ whnf printElements [10,11, 12,4,5]
               ]
  ]

运行代码时出错:

listtraversal.hs:18:67:
    Couldn't match expected type ‘[a0]’ with actual type ‘IO [t0]’
    In the second argument of ‘whnf’, namely ‘(randomList 10)’
    In the second argument of ‘($)’, namely
      ‘whnf printElements (randomList 10)’

【问题讨论】:

  • 尝试给每个函数一个明确的类型签名。这是一个很好的练习,既能在这个学习阶段提供信息,又有助于在 Haskell 学习/使用的所有阶段本地化编译消息。
  • 在基准测试期间,您将重复执行printElements foo 以获取一些foo。您希望foo 在每次执行中都不同吗?
  • 顺便说一句,使用此基准,您将主要衡量打印内容所需的时间,因为相比之下生成随机数和遍历列表所需的时间可以忽略不计。

标签: list haskell benchmarking io-monad criterion


【解决方案1】:

简而言之,您需要将函数绑定IO 值,而不是尝试将其应用到包裹在IO 值中的值。

-- instead of whnf printElements (randomList 10)
randomList 10 >>= whnf printElements

randomList 不返回值列表;它返回一个IO 操作,执行该操作时,可以生成一个值列表。忽略实现引起的各种约束,类型为

randomList :: (...) => t1 -> IO [t]  -- not t1 -> [t]

因此,您不能直接使用IO 操作可以生成的值列表;您需要使用 monad 实例将值 绑定 到适当的函数。 whnf printElements 就是这样一种功能;它接受一个列表并返回一个IO 操作。

whnf printElements :: Show a => [a] -> IO ()

我们没有将列表拉出并将其传递给whnf printElements,而是使用&gt;&gt;= 将函数“推入”一个IO 值。该运算符的类型,专门用于 IO monad,是

(>>=) :: IO a -> (a -> IO b) -> IO b

在这种情况下,第一个IO a 值是randomList 返回的IO [t] 值。 whnf printElements 是我们绑定到的 a -&gt; IO b 函数。 结果是一个新的IO 值,它采用第一个IO 值,取出包装的值,应用给定的函数,然后返回结果。

换句话说,IO monad 本身负责从randomList 中提取结果并将您的函数应用于它,而不是您明确地执行它。


(您可能已经注意到我说过&gt;&gt;= 将值绑定到函数,反之亦然。也许更准确的说法是&gt;&gt;= 将它们绑定到一个IO 动作中。)

【讨论】:

  • 当然randomList 10 &gt;&gt;= whnf printElements也可以写成do { list &lt;- randomList 10; whnf printElements list },或者whnf printElements =&lt;&lt; randomList 10,这样更清楚一点bind是一个application操作符(应用不纯函数不纯参数),如$(纯函数到纯参数)和&lt;$&gt;(纯函数到不纯参数)。
猜你喜欢
  • 1970-01-01
  • 2023-03-15
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-16
  • 1970-01-01
相关资源
最近更新 更多