在列表推导格式函数中定义变量

Define variable in list comprehension format function

提问人:Alejandro Caro 提问时间:10/8/2023 更新时间:10/11/2023 访问量:84

问:

我有这个伪代码,可以计算用户给出的数字的阶乘。

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

列表 哈斯克尔

评论

3赞 Chris 10/8/2023
为什么你的代码要计算阶乘处理输入?通常,这将是一个纯函数,IO 将单独处理。看起来你正在尝试实现一些非常必要的东西,这并不是特别的“Haskell-y”。

答:

3赞 K. A. Buhr 10/8/2023 #1

要使用这样的可变函数实现阶乘函数,您需要在 IO monad 中使用操作来创建 ,然后使用 、 或类似的函数对一组 IO 操作进行排序。它可能看起来像这样:IORefnewIORefIORefforM_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]

我的基准测试表明,基于 -的版本比纯基于 -的版本慢两倍左右。IORefproduct

1赞 willeM_ Van Onsem 10/8/2023 #2

好吧,使用变异变量是非常哈斯克利什的。这是您需要使用 s 的原因之一:因为 ref 本身不会改变,并且修改其值被视为副作用。IO Ref

因此,如果要手动实现阶乘,则使用递归作为重复特定任务的方式,并且通过向参数传递不同的值可以被视为使用修改后的值执行此迭代,因此:

factorial::Int -> Int
factorial 0 = 1
factorial n = n * (factorial (n-1))

但是由于Haskell具有一些更高级别的效用函数,例如,我们可以生成一个项目列表,然后确定这些项目的乘积。

1赞 jpmarinier 10/11/2023 #3

为了补充现有的答案:一个问题是,如果你开始涉及 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

根据您的符号使用状态,即使使用会更惯用。tmpst