2012-11-26 25 views
15

我很难理解为什么这两个片段在所谓的“穷人的严格分析”下产生不同的结果。数据和新类型之间的懒惰/严格

第一个例子使用data(假设正确的应用型实例):

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改变了这个例子的定义?


这是我发现的另一件事情:使用此应用型情况下,data解析器上述输出未定义:

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解析器上面并输出不确定的(假设一个正确的Monad实例Parser s):

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

当提出这样的问题时,如果包含所有相关的代码,如果它足够小以适合(这包括'Functor'和'Monad'实例,'literal''),那么通常会更好,这样人们就不会您不必猜测您是如何编写函数的(正如您已经指出的那样,即使很小的变化也会影响行为)。 – shachaf

+1

@shachaf这里真正的问题不是“我该如何解决我的代码?” - 我已经这样做了 - 但“数据”和“新类型”在严格性/懒惰方面有什么不同?“对不起,如果这不是从问题中清楚。 –

+0

是的,但即便如此,我们如何解释代码的情况,而不知道代码是什么样的? – shachaf

回答

18

正如你可能知道,datanewtype之间的主要区别在于datadata构造是懒惰而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 = ⊥

+0

我还没有清楚的两件事是:1)Haskell的语义是否需要模式匹配差异; 2)模式匹配差异是否是由于构造函数的严格差异。 –

+0

@MattFenwick:1)是的,因为newtypes在语义上基本不存在,所以模式匹配与基础类型的模式匹配相同,因为模式'x'不计算'x',所以模式'N x'。 2)否。考虑数据S a = S!a; constS x(S y)= x',然后S'undefined'seq' x =⊥''','constS x⊥=⊥'。 – hammar

+0

因此,在数据构造函数的情况下,编译器必须进行足够的评估以确定构造函数是否与模式匹配? –

10

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有关。