【问题标题】:Haskell: Exception <<loop>> on recursive data entryHaskell:递归数据输入异常<<loop>>
【发布时间】:2023-03-24 21:07:01
【问题描述】:

所以我正在尝试制作一个小程序,它可以接收在实验期间捕获的数据,并且在大多数情况下,我想我已经弄清楚了如何递归地接收数据,直到用户发出信号为止,但是,在终止数据采集时,haskell 会抛出 Exception: &lt;&lt;loop&gt;&gt;,我真的不知道为什么。代码如下:

readData :: (Num a, Read a) => [Point a] -> IO [Point a]
readData l = do putStr "Enter Point (x,y,<e>) or (d)one: "
                entered <- getLine
                if (entered == "d" || entered == "done")
                    then return l
                    else do let l = addPoint l entered
                            nl <- readData l
                            return nl

addPoint :: (Num a, Read a) => [Point a] -> String -> [Point a]
addPoint l s = l ++ [Point (dataList !! 0) (dataList !! 1) (dataList !! 2)]
    where dataList = (map read $ checkInputData . splitOn "," $ s) :: (Read a) => [a]

checkInputData :: [String] -> [String]
checkInputData xs
    | length xs < 2 = ["0","0","0"]
    | length xs < 3 = (xs ++ ["0"])
    | length xs == 3 = xs
    | length xs > 3 = ["0","0","0"]

据我所知,异常表明某处存在无限循环,但我无法弄清楚为什么会发生这种情况。据我所知,当输入“完成”时,当前级别应该简单地返回 l,它给出的列表,然后应该级联函数的先前迭代。

感谢您的帮助。 (是的,一旦我弄清楚如何做,checkInputData 将有适当的错误处理。)

【问题讨论】:

    标签: haskell recursion


    【解决方案1】:

    &lt;&lt;loop&gt;&gt; 基本上意味着 GHC 检测到由一个直接依赖于自身的值引起的无限循环(如果您好奇,请参阅this questionthis one 了解更多技术细节)。在这种情况下,这是由以下触发的:

    else do let l = addPoint l entered
    

    这个定义隐藏了您作为参数传递的l,它根据自身定义了l。你的意思是写...

    else do let l' = addPoint l entered
    

    ...它根据原始l 定义了一个新值l'

    正如 Carl 指出的那样,打开 -Wall(例如,通过在命令行中将其传递给 GHC,或在 GHCi 中使用 :set -Wall)会使 GHC 就阴影向您发出警告:

    <interactive>:171:33: warning: [-Wname-shadowing]
        This binding for ‘l’ shadows the existing binding
          bound at <interactive>:167:10
    

    此外,正如 dfeuer 所强调的,else 分支中的整个 do-block 可以替换为:

    readData (addPoint l entered)
    

    作为一个不相关的建议,在这种情况下,最好用模式匹配替换您对 length(!!) 的使用。例如,checkInputData 可以写成:

    checkInputData :: [String] -> [String]
    checkInputData xs = case xs of
        [_,_] -> xs ++ ["0"]
        [_,_,_] -> xs
        _ -> ["0","0","0"]
    

    addPoint,反过来,可能变成:

    addPoint :: (Num a, Read a) => [Point a] -> String -> [Point a]
    addPoint l s = l ++ [Point x y z]
        where [x,y,z] = (map read $ checkInputData . splitOn "," $ s) :: (Read a) => [a]
    

    如果您将 checkInputData 更改为返回一个 (String, String, String) 三元组,这将变得更加简洁,这将更好地表达您正在读取三个值的不变量。

    【讨论】:

    • 可能还值得注意的是,添加 -Wall 标志会指出阴影,从而有力地提示出了什么问题。
    • 确实,整个let 以及周围的do 都是多余的。它只需要else readData (addPoint l entered)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-14
    • 2019-02-28
    • 1970-01-01
    • 2014-02-25
    • 2018-01-03
    • 2016-09-20
    相关资源
    最近更新 更多