Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/visual-studio-code/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么第一个Haskell函数无法处理无限列表,而第二个代码段成功处理无限列表?_Haskell_Functional Programming_Fold - Fatal编程技术网

为什么第一个Haskell函数无法处理无限列表,而第二个代码段成功处理无限列表?

为什么第一个Haskell函数无法处理无限列表,而第二个代码段成功处理无限列表?,haskell,functional-programming,fold,Haskell,Functional Programming,Fold,我有两个Haskell函数,这两个函数看起来都非常相似。但是第一种方法在无限列表中失败,第二种方法在无限列表中成功。我已经花了几个小时试图弄清楚这到底是为什么,但没有用 这两个片段都是Prelude中“words”函数的重新实现。这两种方法都可以很好地处理有限列表 以下是不处理无限列表的版本: myWords_FailsOnInfiniteList :: String -> [String] myWords_FailsOnInfiniteList string = foldr step [

我有两个Haskell函数,这两个函数看起来都非常相似。但是第一种方法在无限列表中失败,第二种方法在无限列表中成功。我已经花了几个小时试图弄清楚这到底是为什么,但没有用

这两个片段都是Prelude中“words”函数的重新实现。这两种方法都可以很好地处理有限列表

以下是不处理无限列表的版本:

myWords_FailsOnInfiniteList :: String -> [String]
myWords_FailsOnInfiniteList string = foldr step [] (dropWhile charIsSpace string)
   where 
      step space ([]:xs)      | charIsSpace space = []:xs    
      step space (x:xs)       | charIsSpace space = []:x:xs
      step space []           | charIsSpace space = []
      step char (x:xs)                            = (char : x) : xs
      step char []                                = [[char]] 
myWords_anotherReader :: String -> [String]
myWords_anotherReader xs = foldr step [""] xs
   where 
      step x result | not . charIsSpace $ x = [x:(head result)]++tail result
                    | otherwise             = []:result
以下是处理无限列表的版本:

myWords_FailsOnInfiniteList :: String -> [String]
myWords_FailsOnInfiniteList string = foldr step [] (dropWhile charIsSpace string)
   where 
      step space ([]:xs)      | charIsSpace space = []:xs    
      step space (x:xs)       | charIsSpace space = []:x:xs
      step space []           | charIsSpace space = []
      step char (x:xs)                            = (char : x) : xs
      step char []                                = [[char]] 
myWords_anotherReader :: String -> [String]
myWords_anotherReader xs = foldr step [""] xs
   where 
      step x result | not . charIsSpace $ x = [x:(head result)]++tail result
                    | otherwise             = []:result
注意:“charIsSpace”只是对Char.isSpace的重命名

下面的解释器会话说明第一个解释器在无限列表中失败,而第二个解释器成功

*Main> take 5 (myWords_FailsOnInfiniteList  (cycle "why "))
*** Exception: stack overflow

*Main> take 5 (myWords_anotherReader (cycle "why "))
["why","why","why","why","why"]
编辑:感谢下面的回复,我相信我现在明白了。以下是我的结论和修订后的代码:

结论:

  • 我第一次尝试的最大罪魁祸首是以“步长空间[]”和“步长字符[]”开头的两个方程将阶跃函数的第二个参数与[]匹配是一个否,因为它会强制对整个第二个参数进行求值(但有一个警告将在下面解释)
  • 在某一点上,我曾认为(++)可能会比cons更晚地评估它的右参数。因此,我想我可以通过将“=(char:x):xs”更改为“=[char:x]++xs”来解决这个问题。但这是不正确的
  • 有一次,我认为将第二个参数与(x:xs)匹配的模式会导致函数在无限列表中失败。这一点我几乎是对的,但不完全正确!根据(x:xs)计算第二个参数,就像我在上面的模式匹配中所做的那样,将导致一些递归。它将“转动曲柄”,直到碰到“:”(又名“cons”)。如果这从未发生过,那么我的函数将无法在无限列表中成功。然而,在这个特殊的情况下,一切都正常,因为我的函数最终会遇到一个空格,在这个空格处会出现一个“cons”。通过匹配(x:xs)触发的求值将立即停止,从而避免无限递归。在这一点上,“x”将被匹配,但是xs将保持thunk,因此没有问题。(感谢甘尼什真的帮助我掌握了这一点)
  • 一般来说,只要不强制对第二个参数进行评估,就可以随意提及它。如果您已经匹配了x:xs,那么您可以随意提及xs,只要您不强制对其进行评估
  • 这是修改后的代码。我通常尽量避免使用head和tail,仅仅是因为它们是部分函数,还因为我需要练习编写模式匹配等价物

    myWords :: String -> [String]
    myWords string = foldr step [""] (dropWhile charIsSpace string)
       where 
          step space acc | charIsSpace space = "":acc
          step char (x:xs)                   = (char:x):xs
          step _ []                          = error "this should be impossible"
    
    myWords :: String -> [String]
    myWords string = foldr step [""] (dropWhile charIsSpace string)
       where 
          step space acc | charIsSpace space = "":acc
          step char (x:xs)                   = (char:x):xs
          step _ []                          = error "this should be impossible"
    
    这对于无限列表是正确的。注意,没有头部、尾部或(++)操作符

    现在有一个重要的警告: 当我第一次编写更正后的代码时,我没有第三个等式,它与“step[]”匹配。因此,我收到了关于非穷举模式匹配的警告。显然,避免这种警告是个好主意

    但我想我会有问题的。我已经在上面提到,将第二个arg与[]进行模式匹配是不合适的。但我必须这样做才能摆脱警告

    然而,当我添加“步骤[]方程”时,一切都很好!无限列表仍然没有问题。为什么?

    因为修正代码中的第三个等式永远无法达到

    事实上,考虑下面的版本。它与正确的代码完全相同,只是我将空列表的模式移到了其他模式之上:

    myWords_brokenAgain :: String -> [String]
    myWords_brokenAgain string = foldr step [""] (dropWhile charIsSpace string)
       where 
          step _ []                              = error "this should be impossible"
          step space acc | charIsSpace space     = "":acc
          step char (x:xs)                       = (char:x):xs
    
    我们回到堆栈溢出,因为调用步骤时发生的第一件事是解释器检查等式1是否匹配。为此,必须查看第二个arg是否为[]。为此,它必须计算第二个arg

    将方程式向下移动到其他方程式下方可确保从不尝试第三个方程式,因为第一个或第二个模式始终匹配。第三个等式只是为了省去非穷举模式警告


    这是一次很好的学习经历。感谢大家的帮助。

    第二个版本在开始生成自己的部分答案之前不会实际评估
    结果。第一个版本通过模式匹配立即计算
    结果

    这些无限列表的关键在于,在开始要求列表元素之前,您必须生成一些内容,以便输出总是“领先”于输入


    (我觉得这个解释不是很清楚,但这是我能做的最好的解释。)

    试着用手展开表达式:

     take 5 (myWords_FailsOnInfiniteList  (cycle "why "))
     take 5 (foldr step [] (dropWhile charIsSpace (cycle "why ")))
     take 5 (foldr step [] (dropWhile charIsSpace ("why " ++ cycle "why ")))
     take 5 (foldr step [] ("why " ++ cycle "why "))
     take 5 (step 'w' (foldr step [] ("hy " ++ cycle "why ")))
     take 5 (step 'w' (step 'h' (foldr step [] ("y " ++ cycle "why "))))
    
    下一个扩展是什么?您应该看到,为了匹配
    步骤
    ,您需要知道它是否为空列表。为了找到答案,你必须对它进行评估,至少一点。但是第二个术语恰好是一个
    foldr
    的缩减,它是由模式匹配所针对的函数来实现的。换句话说,step函数在不调用自身的情况下无法查看其参数,因此存在无限递归

    将其与第二个功能的扩展进行对比:

    myWords_anotherReader (cycle "why ")
    foldr step [""] (cycle "why ")
    foldr step [""] ("why " ++ cycle "why ")
    step 'w' (foldr step [""] ("hy " ++ cycle "why ")
    let result = foldr step [""] ("hy " ++ cycle "why ") in
        ['w':(head result)] ++ tail result
    let result = step 'h' (foldr step [""] ("y " ++ cycle "why ") in
        ['w':(head result)] ++ tail result
    
    您可能会看到,这种扩展将一直持续到到达某个空间为止。一旦到达一个空格,“head result”将获得一个值,您将生成答案的第一个元素


    我怀疑第二个函数将为不包含任何空格的无限字符串溢出。你能理解为什么吗?

    其他人已经指出了问题所在,即步骤总是在生成任何输出之前计算其第二个参数,但其第二个参数
    myWords :: String -> [String]
    myWords string = foldr step [""] (dropWhile charIsSpace string)
       where 
          step space acc | charIsSpace space = "":acc
          step char (x:xs)                   = (char:x):xs
          step _ []                          = error "this should be impossible"
    
    \n -> tail $ myWords $ replicate n 'a' ++ " b"
    
    \n -> head $ head $ myWords $ replicate n 'a' ++ " b"
    
    myWords :: String -> [String]
    myWords = foldr step [""] . dropWhile isSpace
       where 
          step space acc | isSpace space = "":acc
          step char ~(x:xs)              = (char:x):xs