数据和newtype之间的懒惰/严格

时间:2012-11-26 14:16:50

标签: haskell lazy-evaluation algebraic-data-types newtype

我很难理解为什么这两个片段在所谓的“穷人严格分析”下会产生不同的结果。

第一个示例使用data(假设一个正确的Applicative实例):

data Parser t a = Parser {
        getParser ::  [t] -> Maybe ([t], a) 
    }

> getParser (pure (,) <*> literal ';' <*> undefined ) "abc"
*** Exception: Prelude.undefined

第二个使用newtype。没有其他区别:

newtype Parser t a = Parser {
        getParser ::  [t] -> Maybe ([t], a) 
    }

> getParser (pure (,) <*> literal ';' <*> undefined ) "abc"
Nothing

literal x是一个解析器,如果其参数与第一个标记匹配,则会成功使用一个输入标记。因此,在此示例中,由于;a不匹配,因此失败。但是,data示例仍然看到下一个解析器未定义,而newtype示例则没有。

我已阅读thisthisthis,但不太了解它们,无法解释为什么第一个示例未定义。在我看来,在这个例子中,newtype 更多懒惰而不是data,与答案所说的相反。 (至少one other person也被这个混淆了。)

为什么从data切换到newtype会更改此示例的定义?


这是我发现的另一件事:使用此Applicative实例,上面的data解析器输出undefined:

instance Applicative (Parser s) where
  Parser f <*> Parser x = Parser h
    where
      h xs = 
        f xs >>= \(ys, f') -> 
        x ys >>= \(zs, x') ->
        Just (zs, f' x')

  pure a = Parser (\xs -> Just (xs, a))

对于此实例,上面的data解析器 not 输出未定义(假设Parser s的正确Monad实例):

instance Applicative (Parser s) where
  f <*> x =
      f >>= \f' ->
      x >>= \x' ->
      pure (f' x')

  pure = pure a = Parser (\xs -> Just (xs, a))

完整的代码段:

import Control.Applicative
import Control.Monad (liftM)

data Parser t a = Parser {
        getParser ::  [t] -> Maybe ([t], a) 
    }


instance Functor (Parser s) where
  fmap = liftM

instance Applicative (Parser s) where
  Parser f <*> Parser x = Parser h
    where
      h xs = f xs >>= \(ys, f') -> 
        x ys >>= \(zs, x') ->
        Just (zs, f' x')

  pure = return


instance Monad (Parser s) where
  Parser m >>= f = Parser h
    where
      h xs =
          m xs >>= \(ys,y) ->
          getParser (f y) ys

  return a = Parser (\xs -> Just (xs, a))


literal :: Eq t => t -> Parser t t
literal x = Parser f
  where
    f (y:ys)
      | x == y = Just (ys, x)
      | otherwise = Nothing
    f [] = Nothing

2 个答案:

答案 0 :(得分:21)

您可能知道,datanewtype之间的主要区别在于data数据构造函数是惰性的,而newtype数据构造函数是严格的,即给出以下类型

data    D a = D a 
newtype N a = N a

然后D ⊥ `seq` x = x,但是N ⊥ `seq` x = ⊥(其中代表“底部”,即未定义的值或错误)

然而,可能不太常见的是在这些数据构造函数上模式匹配时, 角色是“反向”,即

constD x (D y) = x
constN x (N y) = x

然后constD x ⊥ = ⊥(严格),但constN x ⊥ = x(懒惰)。

这就是你的例子中发生的事情。

Parser f <*> Parser x = Parser h where ...

使用data时,<*>定义中的模式匹配将立即发生偏差 参数是,但是newtype的构造函数被忽略了 就像你写的那样

f <*> x = h where

如果要求x = ⊥,则x只会分歧。

答案 1 :(得分:11)

datanewtype之间的区别在于data被“解除”而newtype不是。这意味着data有一个额外的⊥ - 在这种情况下,它意味着undefined / = Parser undefined。当Applicative代码模式与Parser x匹配时,如果构造函数,则会强制值。

当您在data构造函数上进行模式匹配时,会对其进行评估并拆分以确保它不是⊥。例如:

λ> data Foo = Foo Int deriving Show
λ> case undefined of Foo _ -> True
*** Exception: Prelude.undefined

因此data构造函数上的模式匹配是严格的,并且会强制它。另一方面,newtype的表示方式与其构造函数包装的类型完全相同。因此,newtype构造函数上的匹配绝对没有任何意义:

λ> newtype Foo = Foo Int deriving Show
λ> case undefined of Foo _ -> True
True

可能有两种方法可以更改data程序,使其不会崩溃。一种方法是在Applicative实例中使用无可辩驳的模式匹配,这将始终“成功”(但在以后的任何地方使用匹配的值可能会失败)。每个newtype匹配的行为都像一个无可辩驳的模式(因为没有构造函数可以匹配,严格性)。

λ> data Foo = Foo Int deriving Show
λ> case undefined of ~(Foo _) -> True
True

另一种方法是使用Parser undefined代替undefined

λ> case Foo undefined of Foo _ -> True
True

此匹配将成功,因为正在匹配的有效Foo值。它恰好包含undefined,但由于我们不使用它,因此不相关 - 我们只查看最顶层的构造函数。


除了您提供的所有链接外,您可能会发现this article相关。