【问题标题】:Why `data` cause an infinite loop while `newtype` not为什么`data`会导致无限循环,而`newtype`不会
【发布时间】:2019-08-22 05:43:24
【问题描述】:

我正在按照教程programming with arrows 学习Arrow。我根据论文输入了以下代码,除了 SF 是由 data 定义的,而不是像论文中那样由 newtype 定义(实际上,我做了这个更改是偶然的,因为我输入了内存中的代码):

import Control.Category
import Control.Arrow
import Prelude hiding (id, (.))

data SF a b = SF { runSF :: [a] -> [b] }  -- this is the change, using data instead of newtype as in the paper 

-- The folowing code is the same as in the paper
instance Category SF where
  id = SF $ \x -> x
  (SF f) . (SF g) = SF $ \x -> f (g x)

instance Arrow SF where
  arr f = SF $ map f
  first (SF f) = SF $ unzip >>> first f >>> uncurry zip

instance ArrowChoice SF where
  left (SF f) = SF $ \xs -> combine xs (f [y | Left y <- xs])
    where
      combine (Left _ : ys) (z:zs) = Left z : combine ys zs
      combine (Right y : ys) zs = Right y : combine ys zs
      combine [] _ = []

delay :: a -> SF a a
delay x = SF $ init . (x:)

mapA :: ArrowChoice a => a b c -> a [b] [c]
mapA f = arr listcase >>>
         arr (const []) ||| (f *** mapA f >>> arr (uncurry (:)))

listcase :: [a] -> Either () (a, [a])
listcase [] = Left ()
listcase (x:xs) = Right (x, xs)

当我在ghci 中加载文件并执行runSF (mapA (delay 0)) [[1,2,3],[4,5,6]] 时,它会触发无限循环并最终耗尽内存。如果我将data 改回newtype,一切正常。同样的问题发生在 ghc 8.0.2、8.2.2 和 8.6.3 中。

即使我将代码编译成可执行文件,也存在同样的问题。

我认为datanewtype 之间的区别,在定义只有一个字段的数据结构时,就是运行时成本。但这个问题似乎暗示了它们之间的更多差异。或者可能有一些我没有注意到 Arrow 类型类的东西。

任何人都可以有任何想法吗?非常感谢!

【问题讨论】:

  • 我认为这与提供惰性值构造函数的数据有关,newtype 是严格的。例如,请参阅 Rose 对 stackoverflow.com/questions/2649305/… 的回答。也许更熟悉的人可以通过代码并给出更准确的答案。

标签: haskell arrows newtype


【解决方案1】:

让我们看看这个例子。

data A = A [Int]
    deriving (Show)

cons :: Int -> A -> A
cons x (A xs) = A (x:xs)

ones :: A
ones = cons 1 ones

我们希望ones 应该是A [1,1,1,1...],因为我们所做的只是将一个列表包装在data 构造函数中。但我们会错的。回想一下data 构造函数的模式匹配是严格的。也就是说,cons 1 undefined = undefined 而不是A (1 : undefined)。因此,当我们尝试评估 ones 时,cons 模式匹配其第二个参数,这导致我们评估 ones... 我们遇到了问题。

newtypes 不要这样做。在运行时newtype 构造函数是不可见的,所以就好像我们在普通列表上编写了等效程序

cons :: Int -> [Int] -> [Int]
cons x ys = x:ys

ones = cons 1 ones

这是非常高效的,因为当我们尝试评估 ones 时,在我们和下一次评估 ones 之间有一个 : 构造函数。

您可以通过使数据构造函数模式匹配惰性来恢复 newtype 语义:

cons x ~(A xs) = A (x:xs)

这是您的代码的问题(我在做这件事时遇到了这个确切的问题)。 data 模式匹配默认是严格的有几个原因;我看到的最引人注目的是,如果类型有多个构造函数,那么模式匹配将是不可能的。为了修复一些细微的 GC 泄漏,惰性模式匹配也有少量的运行时开销;在 cmets 中链接的详细信息。

【讨论】:

  • 是的,这是惰性匹配的问题。该解决方案是在 Phil 的论文之前发明的,但尚未发表。
  • @augustss,你确​​定 GHC 使用后来的方法吗?由于某些性能原因或其他原因,我很确定它不会。垃圾收集器专门处理一种特殊的“选择器重击”堆对象类型。
  • 不,我不确定。我觉得non-gc的方法比较好,但也许ghc最后没用。
猜你喜欢
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-08
  • 1970-01-01
  • 2014-05-06
  • 2012-09-27
相关资源
最近更新 更多