【问题标题】:How do I update the values of these Int refs (Haskell)?如何更新这些 Int refs (Haskell) 的值?
【发布时间】:2017-11-13 14:58:30
【问题描述】:

我有一些像这样的变量:

let y0 = read (input!!0) :: Int
let h = read (input!!1) :: Int
y <- newIORef y0
minY <- newIORef 0 
maxY <- newIORef (h - 1) 

后来我有了

y_old <- readIORef y

if (some_string!!0 == 'U') then   
    maxY = (y_old - 1)       --I don't think this is working
else if (some_string!!0 == 'D') then
    minY = (y_old + 1)       --I don't think this is working

我基本上是将引用读入一些本地整数,然后尝试根据标准更新引用。

我也试过modifyIORef maxY (y_old - 1),但这也不起作用。编译器只告诉我“解析错误”或“语法错误”,这没有帮助。

我的完整代码:

import System.IO
import Control.Monad
import Data.IORef
import Text.Printf

main :: IO ()
main = do
    hSetBuffering stdout NoBuffering -- DO NOT REMOVE

    -- Auto-generated code below aims at helping you parse
    -- the standard input according to the problem statement.

    input_line <- getLine
    let input = words input_line
    let w = read (input!!0) :: Int -- width of the building.
    let h = read (input!!1) :: Int -- height of the building.
    input_line <- getLine
    let n = read input_line :: Int -- maximum number of turns before game over.
    input_line <- getLine
    let input = words input_line
    let x0 = read (input!!0) :: Int
    let y0 = read (input!!1) :: Int
    x <- newIORef x0
    y <- newIORef y0
    minX <- newIORef 0 
    maxX <- newIORef (w - 1) 
    minY <- newIORef 0 
    maxY <- newIORef (h - 1) 
    loop x0 y0 w h x y minX maxX minY maxY

loop :: Int -> Int -> Int -> Int -> IORef Int -> IORef Int -> IORef Int -> IORef Int-> IORef Int -> IORef Int -> IO ()
loop x0 y0 w h x y minX maxX minY maxY = do
    input_line <- getLine
    let bombdir = input_line :: String -- the direction of the bombs from batman's current location (U, UR, R, DR, D, DL, L or UL)

    x_old <- readIORef x 
    y_old <- readIORef y

    if (bombdir!!0 == 'U') then
        writeIORef maxY (y_old - 1)
    if (bombdir!!0 == 'D') then
        writeIORef minY (y_old + 1)

    if (bombdir!!(bombdir.length-1) == 'L') then
        writeIORef maxX (x_old - 1)
    if (bombdir!!(bombdir.length-1) == 'R') then
        writeIORef minX (x_old + 1)


    x = (minX + maxX) / 2
    y = (minY + maxY) / 2

    x_out <- readIORef x 
    y_out <- readIORef y


    printf "%d %d" x_out y_out
    loop x0 y0 w h x y minX maxX minY maxY

【问题讨论】:

  • 如果你只有两个子句,你只需要if something then something else something而不是另一个if。如果您已经阅读过 ioref,那么您可能想要使用 writeIORef。此外,您确定需要 IORef 吗?很多时候它可以做得更好。
  • 请给minimal reproducible example。如果你有解析错误,你可能没有正确使用do 语法。
  • 这看起来像你想用 Haskell(函数式语言)编写命令式程序。虽然这当然是可能的,但通常这样做是一个非常糟糕的主意。您也没有为最后一个if 案例提供else。使用(!!) 通常也是一种反模式,因为它是一个非全部(并且对于任意索引效率低下)函数。
  • @leftaroundabout 我不能在这里提供完整的 MCVE,因为它是在线游戏的一部分
  • @WillemVanOnsem 你说得对,我正在尝试编写命令式风格——这是因为我不知道如何使用非命令式风格编写我想要的程序。确实浪费了 4 天的时间试图做到这一点而没有任何进展——所以在我完成问题/看看其他人是怎么做的/从中吸取教训之前,我会用 IORefs 来嘲笑它。

标签: variables haskell syntax reference


【解决方案1】:

先来点好风格:

    ...
    input_line <- getLine
    let input = words input_line
    let w = read (input!!0) :: Int -- width of the building.
    let h = read (input!!1) :: Int -- height of the building.

这行得通,但很尴尬。您建立一个列表并为其命名,只是为了访问单个元素,逐个解析它们并赋予它们新名称。为什么不只是

    input_line <- getLine
    let [w,h] = map read (words input_line) :: [Int]

事实上,你甚至不需要input_line,也不需要签名(假设你以后使用wh,所以它们的类型是明确的)。即,以下也可以解决问题:

    [w,h] <- map read . words <$> getLine

同样,

    [x₀,y₀] <- map read . words <$> getLine

您在loop 中的代码的真正问题是您尝试使用if 作为程序控制流语句。 Haskell 没有控制流,它只有表达式。因此,Haskell 的if 实际上与命令式语言中的if 语句非常不同,它更像? : 运算符。你可以做的一件事是完成这些条件来

    if (bombdir!!0 == 'U')
     then writeIORef maxY (y_old - 1)
     else pure ()

...pure () 是伪命令式(单子)do 块中的无操作。但是对于这个特定的构造,有一个标准的组合器,when。它实际上只是一个库函数,但其​​行为几乎与命令式语言中的if 完全相同。有了它,你就可以做到了

    when (bombdir!!0 == 'U') $ writeIORef maxY (y_old - 1)

但我不会推荐它。您再次进行尴尬的列表元素访问。根据列表元素的值决定要做什么的更好方法是直接使用case 表达式对它们进行模式匹配:

loop :: Int -> Int -> Int -> Int
 -> IORef Int -> IORef Int -> IORef Int -> IORef Int-> IORef Int -> IORef Int -> IO ()
loop x₀ y₀ w h x y minX maxX minY maxY = do

    bombdir <- getLine  -- no need to first name it `input_line`

    x_old <- readIORef x 
    y_old <- readIORef y

    case bomdir of
      ('U':_) -> writeIORef maxY (y_old - 1)
      ('D':_) -> writeIORef minY (y_old + 1)
      _       -> pure ()

    case reverse bombdir of
      ('L':_) -> writeIORef maxX (x_old - 1)
      ('R':_) -> writeIORef minX (x_old + 1)
      _       -> pure ()

然后,您尝试直接在 IORefs 上进行算术运算,而不是在纯值上。这通常被避免;你真正需要写的是

    writeIORef x =<< do
        xmin <- readIORef minX
        xmax <- readIORef maxX
        pure $ (xmin + xmax) / 2

我认为我们同意这看起来很糟糕,但它确实有效。更简洁的写法是

    writeIORef x =<< (/2) <$> ((+) <$> readIORef minX <*> readIORef maxX)

正如我已经评论过的,幸运的是,您根本不需要那些丑陋的 IORef。只需将变量的“新版本”传入递归调用即可:

loop :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int-> Int -> Int -> IO ()
loop x₀ y₀ w h x y minX maxX minY maxY = do
    bombdir <- input_line

    let (maxY',minY') = case bombdir of
          ('U':_) -> (y - 1, minY )
          ('D':_) -> (maxY , y + 1)
        (maxX',minX') = case reverse bombdir of
          ('L':_) -> (x - 1, minX )
          ('R':_) -> (maxX , x + 1)

        x' = (minX' + maxX') / 2
        y' = (minY' + maxY') / 2

    printf "%d %d" x' y'
    loop x₀ y₀ w h x' y' minX' maxX' minY' maxY'

【讨论】:

  • 这非常有帮助——谢谢。顺便说一句,编译器给了我一个关于模式匹配不完整的错误,但是在我为每个 _ -&gt; (maxY, minY) 添加了一个案例之后它就起作用了,这是正确的做法吗?
  • 所以一般的想法是我们不改变变量——我们递归/调用带有修改参数的新函数,这就是我们更新状态的方式?
猜你喜欢
  • 2021-05-22
  • 2010-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-23
  • 2010-09-27
  • 1970-01-01
  • 2016-05-29
相关资源
最近更新 更多