首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Haskell中基于可变IORef的阶乘示例

Haskell中基于可变IORef的阶乘示例
EN

Code Review用户
提问于 2023-02-21 15:07:52
回答 2查看 58关注 0票数 3

下面是我使用Data.IORef编写臭名昭著的阶乘的尝试。

代码语言:javascript
复制
import Data.IORef

xgo accRef nRef = go where
  go = do
    acc <- readIORef accRef
    n <- readIORef nRef
    writeIORef accRef (acc * n)
    writeIORef nRef (n - 1)
    if (n > 2) then go else readIORef accRef
       
facM n = do
  accRef <- newIORef 1
  nRef <- newIORef n
  xgo accRef nRef

这段代码被认为是关于如何使用IORefs的假人的一个例子,所以我当然想保持它简单--例如,没有元组,也没有镜头。但整个xgo的事情似乎都是人为的。我们可以在xgo中定义facM吗?编写这样的IORef代码最惯用的方法是什么(当然,我不是指避免IORef,我们在这里都知道fac n = prod [1..n]解决方案)。

Upd:我拿到了:

代码语言:javascript
复制
facM n = do
  accRef <- newIORef 1
  nRef <- newIORef n
  let xgo = go where {
    go = do
      acc <- readIORef accRef
      n <- readIORef nRef
      writeIORef accRef (acc * n)
      writeIORef nRef (n - 1)
      if (n > 2) then go else readIORef accRef
  }
  xgo

因此,不再存在第二个accRef/nRef,但仍然存在xgo/go分离。不知怎么的,如果没有中间的xgo = do { ... },我就不能让go工作

EN

回答 2

Code Review用户

回答已采纳

发布于 2023-02-21 19:10:44

首先,有一个bug,如果不是一个非常重要的- 0!表示为1,但当前代码返回0。在你做任何乘法之前,检查n>1就可以绕过这个问题了。

第二,由于acc只用于更新accRef,所以我认为使用modifyIORef而不是readIORef后面跟着writeIORef可能会更干净。但是,这不适用于nRef,因为n实际上在多个地方使用

此外,我希望看到显式类型签名添加到任何顶级函数(facM,以及原始代码中的xgo)中。我发现,知道一个值的确切类型通常会使我们更容易理解它是什么以及为什么。

最后,您提到能够将单独的xgo函数合并到facM中,但最终还必须有一个有效相同的go函数才能工作。我想那可能是你碰到了哈斯克尔那出了名的混乱的缩进规则。例如,以下内容不起作用:

代码语言:javascript
复制
import Data.IORef

facM :: (Num a, Ord a) => a -> IO a
facM n = do
  accRef <- newIORef 1
  nRef <- newIORef n
  let go = do
    n <- readIORef nRef
    if n > 1 then do
      modifyIORef accRef (* n)
      writeIORef nRef (n - 1)
      go
    else readIORef accRef
  go

因为该do块的内容位于它绑定到(go)的名称的左侧。但是,下列任何一种方法都可以工作,因为该块的缩进比其绑定的名称更大:

代码语言:javascript
复制
import Data.IORef

facM :: (Num a, Ord a) => a -> IO a
facM n = do
  accRef <- newIORef 1
  nRef <- newIORef n
  let
    go = do
      n <- readIORef nRef
      if n > 1 then do
        modifyIORef accRef (* n)
        writeIORef nRef (n - 1)
        go
      else readIORef accRef
  go
代码语言:javascript
复制
import Data.IORef

facM :: (Num a, Ord a) => a -> IO a
facM n = do
  accRef <- newIORef 1
  nRef <- newIORef n
  let go = do
        n <- readIORef nRef
        if n > 1 then do
          modifyIORef accRef (* n)
          writeIORef nRef (n - 1)
          go
        else readIORef accRef
  go
票数 2
EN

Code Review用户

发布于 2023-02-22 12:13:13

不确定您想要完成什么,但是您可以使用来自mfixControl.Monad.Fix替换显式循环。

代码语言:javascript
复制
import Data.IORef
import Control.Monad.Fix (mfix)

facM :: Int -> IO Int
facM n = do
  accRef <- newIORef 1
  nRef <- newIORef n
  mfix
    (\loop _ -> do
      m <- readIORef nRef
      acc <- readIORef accRef
      modifyIORef accRef (* m)
      modifyIORef nRef (subtract 1)
      if m > 2 then loop else readIORef accRef
    )
    undefined

这有可怕的undefined (它可以被()取代),所以对于虚拟人来说绝对不是一个好的例子。

或者像这样,如果你想让他们更困惑:

代码语言:javascript
复制
facM :: Int -> IO Int
facM n
  = newIORef (n, 1)
  >>= mfix (\loop ref -> do
    (m, acc) <- readIORef ref
    writeIORef ref (m - 1, acc * m)
    if (m > 2) then loop else pure (acc * m)
  )
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/283470

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档