2012-07-10 21 views
5

Tôi muốn tạo các hàm tạo ứng dụng chung cho các bản ghi haskell để tạo một trình phân tích cú pháp cho bản ghi.Hàm tạo ứng dụng cho các bản ghi

Hãy xem xét các hồ sơ:

data Record = Record {i :: Int, f :: Float} 

constructor Tôi muốn:

Record <$> pInt <*> pFloat 

parsers với nhiều loại cơ bản được đưa ra:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

Có bất kỳ thư viện mà đã có thể làm được điều này ? Có thể định nghĩa getParser cho một bản ghi không? Cảm ơn trước.

+0

Chỉ cần làm rõ: bạn muốn một thể hiện cho 'parseable Record' được tạo ra cho bạn? – kosmikus

Trả lời

9

Điều này có thể được thực hiện bằng cách sử dụng thư viện regular. Làm việc với thư viện này thường đòi hỏi một số mở rộng ngôn ngữ:

{-# LANGUAGE FlexibleContexts  #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE TypeFamilies   #-} 
{-# LANGUAGE TypeOperators  #-} 
{-# LANGUAGE UndecidableInstances #-} 

import Control.Applicative 
import Generics.Regular 

Ít nhất hai trong số các thư viện phân tích cú pháp-combinator phổ biến nhất đi kèm với một giao diện applicative-functor: xem, ví dụ, uu-parsinglibparsec, nhưng để giữ cho mọi thứ dễ dàng , hãy sử dụng các trình phân tích cú pháp danh sách thành công đơn giản tại đây.

newtype Parser a = Parser {runParser :: ReadS a} 

instance Functor Parser where 
    fmap f p = Parser $ \s -> [(f x, s') | (x, s') <- runParser p s] 

instance Applicative Parser where 
    pure x = Parser $ \s -> [(x, s)] 
    p <*> q = Parser $ \s -> 
    [(f x, s'') | (f, s') <- runParser p s, (x, s'') <- runParser q s'] 

instance Alternative Parser where 
    empty = Parser $ \_ -> [] 
    p <|> q = Parser $ \s -> runParser p s ++ runParser q s 

(. Lưu ý rằng type ReadS a = String -> [(a, String)])

pSym :: Char -> Parser Char 
pSym c = Parser $ \s -> case s of 
    (c' : s') | c == c' -> [(c', s')] 
    _     -> [] 

pInt :: Parser Int 
pInt = Parser reads 

pFloat :: Parser Float 
pFloat = Parser reads 

thẳng thắn, ta có:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

Và, đối với loại hồ sơ của bạn, như mong muốn:

data Record = Record {i :: Int, f :: Float} 

instance Parseable Record where 
    getParser = Record <$> pInt <* pSym ' ' <*> pFloat 

Bây giờ , chúng ta làm cách nào tạo một trình phân tích cú pháp như vậy?

tiên, chúng ta xác định cái gọi là mô hình functor của Record (xem tài liệu của regular để biết chi tiết):

type instance PF Record = K Int :*: K Float 

Sau đó, chúng tôi làm Record một thể hiện của kiểu lớp Regular:

instance Regular Record where 
    from (Record n r) = K n :*: K r 
    to (K n :*: K r) = Record n r 

Tiếp theo, chúng tôi xác định trình phân tích cú pháp chung:

class ParseableF f where 
    getParserF :: Parser a -> Parser (f a) 

instance ParseableF (K Int) where 
    getParserF _ = K <$> pInt 

instance ParseableF (K Float) where 
    getParserF _ = K <$> pFloat 

instance (ParseableF f, ParseableF g) => ParseableF (f :*: g) where 
    getParserF p = (:*:) <$> getParserF p <* pSym ' ' <*> getParserF p 

(Để trang trải tất cả các loại thông thường, bạn sẽ phải cung cấp một số trường hợp hơn, nhưng chúng sẽ làm ví dụ của bạn.)

Bây giờ, chúng ta có thể chứng minh rằng tất cả các loại trong lớp Regular (được đưa ra một ví dụ ParseableF cho pattern functor của nó) đi kèm với một trình phân tích cú pháp:

instance (Regular a, ParseableF (PF a)) => Parseable a where 
    getParser = to <$> getParserF getParser 

Hãy lấy nó để quay. Thả các phiên bản gốc của Parseable (ví dụ: số của Int, Float và dĩ nhiên là Record) và chỉ giữ một phiên bản chung duy nhất.Ở đây chúng ta đi:

> runParser (getParser :: Parser Record) "42 3.14" 
[(Record {i = 42, f = 3.14},"")] 

Lưu ý: đây chỉ là một ví dụ rất cơ bản về cách để lấy được phân tích cú pháp chung sử dụng thư viện thường xuyên. Các thư viện chính nó đi kèm với một generic list-of-successes parser mà làm những điều đặc biệt tốt đẹp với hồ sơ. Bạn có thể muốn kiểm tra điều đó trước tiên. Hơn nữa, thư viện đi kèm với hỗ trợ mẫu Haskell để các phiên bản Regular có thể được bắt nguồn tự động. Những trường hợp này bao gồm các loại cấu trúc đặc biệt cho các nhãn bản ghi, để bạn có thể có các hàm chung của bạn xử lý các loại bản ghi thực sự ưa thích. Kiểm tra các tài liệu.

3

Nhiều như tôi thích gói regular, tôi muốn chỉ ra rằng vì ghc-7.2 GHC đã tích hợp sẵn hỗ trợ cho việc phát sinh các loại đại diện chung, do đó bạn không phải dựa vào Mẫu Haskell để làm điều đó.

Thay đổi so với giải pháp được đề xuất bởi dblhelix như sau. Bạn cần các cờ và mô-đun hơi khác nhau được nhập:

{-# LANGUAGE DeriveGeneriC#-} 
{-# LANGUAGE DefaultSignatures #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE TypeOperators #-} 

import Control.Applicative 
import GHC.Generics 

Bạn vẫn xác định Parser và các trường hợp như trên. Bạn cần phải lấy được lớp Generic cho loại Record của bạn:

data Record = Record { i :: Int, f :: Float } 
    deriving (Generic, Show) 

Lớp Generic rất giống với lớp Regular. Bạn không phải xác định PF hoặc phiên bản Regular ngay bây giờ.

Thay vì ParseableF, chúng ta định nghĩa một lớp Parseable' đó là rất giống nhau trong phong cách, chưa bao giờ nên hơi khác nhau:

class Parseable' f where 
    getParser' :: Parser (f a) 

-- covers base types such as Int and Float: 
instance Parseable a => Parseable' (K1 m a) where 
    getParser' = K1 <$> getParser 

-- covers types with a sequence of fields (record types): 
instance (Parseable' f, Parseable' g) => Parseable' (f :*: g) where 
    getParser' = (:*:) <$> getParser' <* pSym ' ' <*> getParser' 

-- ignores meta-information such as constructor names or field labels: 
instance Parseable' f => Parseable' (M1 m l f) where 
    getParser' = M1 <$> getParser' 

Cuối cùng, cho Parseable, chúng ta định nghĩa một phương thức mặc định chung:

class Parseable a where 
    getParser :: Parser a 
    default getParser :: (Generic a, Parseable' (Rep a)) => Parser a 
    getParser = to <$> getParser' 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

Hiện tại, việc phân tích cú pháp loại Record cũng đơn giản như việc cung cấp khai báo cá thể trống:

instance Parseable Record 

Ví dụ hoạt động như trước đây:

> runParser (getParser :: Parser Record) "42 3.14" 
[(Record {i = 42, f = 3.14},"")]