2012-11-26 13 views
15

Ich habe zu kämpfen, um zu verstehen, warum diese zwei Schnipsel unterschiedliche Ergebnisse unter der sogenannten "Strenge Analyse des armen Mannes" produzieren.Faulheit/Strenge zwischen Daten und newtype

Das erste Beispiel verwendet data (eine korrekte Instanz Applicative vorausgesetzt):

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

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

Die zweite verwendet newtype. Es gibt keinen anderen Unterschied:

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

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

literal x einen Parser ist das ein Zeichen des Eingangs gelingt es raubend, wenn sein Argument mit dem ersten Token übereinstimmt. In diesem Beispiel schlägt es fehl, da ; nicht mit a übereinstimmt. Das data Beispiel sieht jedoch immer noch, dass der nächste Parser nicht definiert ist, während das newtype Beispiel nicht funktioniert.

Ich habe gelesen this, this und this, aber verstehe sie nicht gut genug, um zu bekommen, warum das erste Beispiel nicht definiert ist. Es scheint mir, dass in diesem Beispiel newtype ist mehr faul als data, das Gegenteil von dem, was die Antworten sagten. (Mindestens one other person wurde auch dadurch verwirrt).

Warum ändert sich die Definition dieses Beispiels von data zu newtype?


Hier ist eine andere Sache, die ich entdeckt: mit diesem Applicative Beispiel die data Parser über Ausgänge 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)) 

während bei diesem Fall der data Parser oben tut nicht Ausgang undefiniert (unter der Annahme, eine korrekte Monad-Instanz für Parser s):

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

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

Voll Code-Schnipsel:

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

Wenn eine Frage wie diese zu fragen, ist es im Allgemeinen besser, wenn Sie alle relevanten Codes, wenn es klein genug ist, zu passen (dazu gehört auch die '' Functor' und Monad' Instanzen und 'literal'), so dass die Menschen don Sie müssen nicht genau abschätzen, wie Sie die Funktionen geschrieben haben (wie Sie bereits erwähnt haben, können auch kleine Änderungen das Verhalten beeinflussen). – shachaf

+1

@shachaf die echte Frage hier ist nicht "Wie repariere ich meinen Code?" - Das habe ich schon gemacht - aber "was ist anders in Bezug auf Strenge/Faulheit zwischen" Daten "und" Newtype "?" Entschuldigung, wenn das aus der Frage nicht klar ist. –

+0

Ja, aber wie können wir Ihnen erklären, was mit Ihrem Code passiert ist, ohne zu wissen, wie der Code aussieht? – shachaf

Antwort

18

Wie Sie wahrscheinlich wissen, ist der Hauptunterschied zwischen data und newtype dass mit data ist, dass data Konstrukteuren faul sind, während newtype streng, dh die folgenden Arten gegeben

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

dann D ⊥ `seq` x = x, aber N ⊥ `seq` x = ⊥.

Was weniger ist vielleicht allgemein bekannt ist jedoch, dass, wenn Sie Mustererkennung auf diesen Konstrukteuren, die Rollen sind „umgekehrt“, das heißt mit

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

dann constD x ⊥ = ⊥, aber constN x ⊥ = x.

Dies ist, was in Ihrem Beispiel passiert.

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

Mit data wird die Mustererkennung in der Definition von <*> auseinander gehen sofort, wenn entweder der Argumente sind, aber mit newtype die Konstrukteure werden ignoriert und es ist , als ob Sie geschrieben hatte

f <*> x = h where 

, die nur für x = ⊥ abweichen, wenn x angefordert wird.

+0

Zwei Dinge, auf die ich noch nicht ganz klar bin, sind: 1) ob die Mustervergleichsdifferenz aufgrund der Haskell-Semantik erforderlich ist, und 2) ob die Mustervergleichsdifferenz auf der Striktheitsdifferenz des Konstruktors beruht. –

+0

@MattFenwick: 1) Ja, da newtypes im Grunde nicht semantisch existieren, ist der Musterabgleich auf dem gleichen wie der Musterabgleich auf dem zugrunde liegenden Typ, so dass, da das Muster 'x' kein' x' auswertet, auch nicht Muster 'N x'. 2) Nein. Betrachte 'Daten S a = S! A; constS x (S y) = x ', dann '' 'S undefiniert' seq' x = ⊥''' und 'constS x ⊥ = ⊥'. – hammar

+0

Also muss im Fall eines Datenkonstruktors der Compiler weit genug auswerten, um festzustellen, ob der Konstruktor dem Muster entspricht? –

10

Der Unterschied zwischen data und newtype ist, dass data ist "aufgehoben" und newtype ist nicht. Das bedeutet, dass die data eine zusätzliche ⊥ hat - in diesem Fall bedeutet dies, dass undefined/= Parser undefined. Wenn Ihr Codemuster auf Parser x übereinstimmt, erzwingt es einen Wert wenn der Konstruktor.

Wenn Sie einen data Konstruktor mit einem Muster vergleichen, wird dieser ausgewertet und zerlegt, um sicherzustellen, dass es nicht ⊥ ist. Zum Beispiel:

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

So Mustervergleich auf ein data Konstruktor ist streng, und es zwingen wird. Eine newtype wird dagegen genau so dargestellt, wie der Typ, den ihr Konstruktor umschließt. So passend auf ein newtype Konstruktor absolut nichts:

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

Es gibt wahrscheinlich zwei Möglichkeiten data Programm so zu ändern, dass es nicht zum Absturz bringen. Eine wäre die Verwendung einer unwiderlegbaren Musterübereinstimmung in Ihrer Instanz, die immer "erfolgreich" sein wird (aber die Verwendung der angepassten Werte irgendwo später könnte fehlschlagen). Jede newtype Übereinstimmung verhält sich wie ein unwiderlegbares Muster (da es keinen Konstruktor gibt, auf den strikt zu achten ist).

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

Die andere wäre Parser undefined von undefined stattdessen zu verwenden:

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

Dieses Spiel wird erfolgreich sein, weil es ein gültiger Foo Wert ist, der auf abgestimmt ist wird. Es enthält undefined, aber das ist nicht relevant, da wir es nicht verwenden - wir betrachten nur den obersten Konstruktor.


Zusätzlich zu allen Links, die Sie gaben, könnten Sie this article relevant finden.