Đ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-parsinglib và parsec, 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.
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