2012-11-26 31 views
15

Tôi đang cố gắng hiểu tại sao hai đoạn này tạo ra các kết quả khác nhau dưới cái gọi là "phân tích độ nghiêm ngặt của người nghèo".Độ mềm/độ chặt giữa dữ liệu và kiểu mới

Ví dụ đầu tiên sử dụng data (giả sử một trường hợp applicative đúng):

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

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

Thứ hai sử dụng newtype. Không có sự khác biệt khác:

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

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

literal x là một phân tích cú pháp mà thành công tiêu thụ một chiếc thẻ đầu vào nếu đối số của nó phù hợp với dấu hiệu đầu tiên. Vì vậy, trong ví dụ này, nó không thành công vì ; không khớp với a. Tuy nhiên, ví dụ data vẫn thấy rằng trình phân tích cú pháp tiếp theo không được xác định, trong khi ví dụ newtype thì không.

Tôi đã đọc this, thisthis, nhưng không hiểu chúng đủ tốt để biết tại sao ví dụ đầu tiên không được xác định. Dường như với tôi rằng trong ví dụ này, newtypenhiều hơn lười hơn data, ngược lại với câu trả lời cho biết. (Ít nhất one other person cũng bị nhầm lẫn bởi điều này).

Tại sao chuyển đổi từ data thành newtype thay đổi định nghĩa của ví dụ này?


Đây là một điều tôi khám phá ra: với dụ applicative này, data phân tích cú pháp trên kết quả đầu ra không xác định:

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)) 

trong khi với trường hợp này, phân tích cú pháp data trên không không sản lượng không xác định (giả sử một bản sao Monad chính xác cho Parser s):

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

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

Full đoạn mã:

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

Khi đặt câu hỏi như thế này, tốt hơn nếu bạn bao gồm tất cả các mã có liên quan, nếu nó đủ nhỏ để vừa (bao gồm trường 'Functor' và' Monad', và 'literal'), để mọi người don không phải đoán chính xác cách bạn viết các hàm (như bạn đã chỉ ra, ngay cả những thay đổi nhỏ có thể tạo ra sự khác biệt về hành vi). – shachaf

+1

@shachaf câu hỏi thực sự ở đây không phải là "làm cách nào để sửa mã của tôi?" - Tôi đã làm điều đó - nhưng "sự khác biệt giữa' dữ liệu' và 'newtype' đối với sự nghiêm khắc/lười biếng là gì?" Xin lỗi nếu điều đó không rõ ràng từ câu hỏi. –

+0

Có, nhưng ngay cả như vậy, làm thế nào chúng ta có thể giải thích những gì đang xảy ra với mã của bạn mà không biết mã trông như thế nào? – shachaf

Trả lời

18

Như bạn đã biết, sự khác biệt chính giữa datanewtype là với datadata constructors là lười biếng trong khi newtype là nghiêm ngặt, tức là cho các loại sau đây

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

sau đó D ⊥ `seq` x = x, nhưng N ⊥ `seq` x = ⊥.

gì có lẽ là ít thường được gọi, tuy nhiên, đó là khi bạn mô hình phù hợp với các nhà thầu, vai trò là "đảo ngược", ví dụ: với

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

sau đó constD x ⊥ = ⊥, nhưng constN x ⊥ = x.

Đây là những gì đang xảy ra trong ví dụ của bạn.

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

Với data, các mô hình phù hợp trong định nghĩa của <*> sẽ phân ra ngay lập tức nếu một trong hai của các đối số là , nhưng với newtype các nhà thầu sẽ được bỏ qua và nó là cũng giống như khi bạn đã viết

f <*> x = h where 

sẽ chỉ phân kỳ cho x = ⊥ nếu yêu cầu x.

+0

Hai điều tôi vẫn chưa hoàn toàn rõ ràng là 1) liệu sự khác biệt phù hợp với mô hình có cần thiết theo ngữ nghĩa của Haskell hay không, và 2) liệu sự khác biệt phù hợp với mô hình có phải là do sự khác biệt nghiêm ngặt của nhà xây dựng hay không. –

+0

@MattFenwick: 1) Có, vì newtypes về cơ bản không tồn tại ngữ nghĩa, kiểu khớp trên một là giống như kiểu khớp trên kiểu cơ bản, vì vì mẫu 'x' không đánh giá' x', cũng không phải mẫu 'N x'. 2) Không. Xem xét 'dữ liệu S a = S! A; constS x (S y) = x', sau đó '' 'S không xác định' seq' x = ⊥''' và 'constS x ⊥ = ⊥'. – hammar

+0

Vì vậy, trong trường hợp của một nhà xây dựng dữ liệu trình biên dịch phải đánh giá đủ xa để xác định liệu các nhà xây dựng phù hợp với mô hình? –

10

Sự khác biệt giữa datanewtypedata được "dỡ bỏ" và newtype thì không. Điều đó có nghĩa là data có thêm ⊥ - trong trường hợp này, điều đó có nghĩa là undefined/= Parser undefined. Khi các mẫu mã Applicative khớp của bạn trên Parser x, nó buộc giá trị nếu hàm tạo.

Khi bạn khớp mẫu trên một hàm tạo data, nó được đánh giá và tách rời để đảm bảo nó không phải là ⊥. Ví dụ:

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

Vì vậy, đối sánh mẫu trên một constructor data là nghiêm ngặt và sẽ bắt buộc. Mặt khác, newtype được biểu diễn chính xác giống như kiểu mà hàm tạo của nó kết thúc tốt đẹp. Vì vậy, phù hợp trên một constructor newtype không hoàn toàn không có gì:

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

Có lẽ hai cách để thay đổi chương trình data của bạn như vậy mà nó không sụp đổ. Người ta sẽ sử dụng một kết hợp mẫu không thể chối cãi trong cá thể Applicative của bạn, sẽ luôn "thành công" (nhưng sử dụng các giá trị khớp nhau ở bất kỳ nơi nào sau này có thể không thành công). Mỗi trận đấu newtype hoạt động giống như một khuôn mẫu không thể chối cãi (vì không có hàm tạo nào phù hợp, nghiêm ngặt).

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

Các khác sẽ được sử dụng thay vì Parser undefinedundefined:

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

trận đấu này sẽ thành công, bởi vì có một giá trị hợp lệ Foo đó được kết hợp trên. Nó xảy ra để chứa undefined, nhưng điều đó không liên quan vì chúng ta không sử dụng nó - chúng ta chỉ nhìn vào nhà xây dựng trên cùng.


Ngoài tất cả các liên kết bạn đã cho, bạn có thể tìm this article có liên quan.

Các vấn đề liên quan