2012-11-26 24 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有關。