为什么`data`导致无限循环而`newtype`不是

时间:2019-04-01 05:40:06

标签: haskell arrows newtype

我正在按照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类型类。

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

1 个答案:

答案 0 :(得分:12)

让我们看看这个例子。

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 ...我们遇到了问题。

newtype不会这样做。在运行时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泄漏;评论中链接的详细信息。

相关问题