提问人:Alejandro Caro 提问时间:10/8/2023 更新时间:10/11/2023 访问量:84
在列表推导格式函数中定义变量
Define variable in list comprehension format function
问:
我有这个伪代码,可以计算用户给出的数字的阶乘。
Process without_title
Define i, tmp, x As Integers;
tmp<-1;
Write "Enter a number to calculate its factorial";
Read x;
For i <- 1 Until x Do
tmp<-tmp * i;
EndTo
Write tmp;
EndProcess
现在我想使用列表推导式在 Haskell 中实现相同的代码
import Data.IORef
factorial::Int -> Int
factorial x = [tmp|i<-[1..x], modifyIORef tmp (*i)]
抛出错误,指出变量未定义tmp
如何定义变量,以便可以使用它?tmp
答:
要使用这样的可变函数实现阶乘函数,您需要在 IO monad 中使用操作来创建 ,然后使用 、 或类似的函数对一组 IO 操作进行排序。它可能看起来像这样:IORef
newIORef
IORef
forM_
traverse_
sequence_
IORef
import Control.Monad
import Data.IORef
factorial :: Int -> IO Int
factorial x = do
tmp <- newIORef 1
forM_ [1..x] $ \i -> modifyIORef' tmp (*i)
readIORef tmp
或者,如果您更喜欢使用列表推导式,因此它看起来更像您上面的尝试,例如:
import Data.IORef
factorialIO :: Int -> IO Int
factorialIO x = do
tmp <- newIORef 1
sequence_ [modifyIORef' tmp (*i) | i <- [1..x]]
readIORef tmp
(使用 strict 提供比 lazy 更好的性能。modifyIORef'
modifyIORef
然后可以这样称呼:
main :: IO ()
main = do
result <- factorial 10
print $ result
这通常比更直接的“纯”解决方案慢,例如:
factorial :: Int -> Int
factorial x = product [1..x]
我的基准测试表明,基于 -的版本比纯基于 -的版本慢两倍左右。IORef
product
好吧,使用变异变量是非常不哈斯克利什的。这是您需要使用 s 的原因之一:因为 ref 本身不会改变,并且修改其值被视为副作用。IO Ref
因此,如果要手动实现阶乘,则使用递归作为重复特定任务的方式,并且通过向参数传递不同的值可以被视为使用修改后的值执行此迭代,因此:
factorial::Int -> Int
factorial 0 = 1
factorial n = n * (factorial (n-1))
但是由于Haskell具有一些更高级别的效用函数,例如,我们可以生成一个项目列表,然后确定这些项目的乘积。
为了补充现有的答案:一个问题是,如果你开始涉及 IO,你就不能像你的函数那样拥有无 IO 类型的签名。factorial :: Int -> Int
另一个(更容易处理)的问题是,如果直接返回列表推导表达式,则函数的结果类型必须是某种列表。它不能是标量值,例如 。Int
一种可能的折衷方案是使用 Haskell 状态单子,将乘积作为状态。您可以使用合适的列表推导表达式生成所需乘法运算的列表,然后使用 sequence_
库函数将它们组合在一起。tmp
喜欢这个:
import Control.Monad.State (state, runState, sequence)
factorial :: Int -> Int
factorial x = finalTmp where
tmp0 = 1 -- our initial state
actions = [ state (\tmp -> ((), i*tmp)) | i <- [1..x] ]
(rs, finalTmp) = runState (sequence_ actions) tmp0
根据您的符号使用状态,即使使用会更惯用。tmp
st
评论