2012-07-10 234 views
5

我想一般創建haskell記錄的應用構造函數,以創建記錄的解析器。適用於記錄的構造函數

考慮記錄:

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

我想構造:

基本類型
Record <$> pInt <*> pFloat 

解析器給出:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

是否有已經可以做到這一點任何庫?是否可以定義getParser作爲記錄?提前致謝。

+0

只是爲了澄清:你想爲'可解析Record'一個實例爲您生成? – kosmikus 2012-07-10 15:35:08

回答

9

這可以使用例如regular庫來完成。與此庫的工作通常需要一些語言擴展:

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

import Control.Applicative 
import Generics.Regular 

至少有兩個最流行的解析器組合子庫配備了應用性,函子接口:見,例如,uu-parsinglibparsec,而是讓事情變得容易,讓我們在這裏使用簡單的成功列表解析器。

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 

(注意: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 

直截了當,我們有:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

而且,對於記錄類型,根據需要:

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

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

現在,我們如何一般生成這樣的解析器?

首先,我們定義的Record所謂的圖案仿函數(見regular獲取細節):

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

然後,我們做Record類型類Regular的實例:

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

接下來,我們定義一個通用解析器:

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 

(覆蓋所有常規類型,你將不得不提供一些更多的情況,但這些會爲你的例子做的。)

現在,我們可以證明,在類Regular每個類型(給出一個ParseableF實例它的模式仿函數)帶有一個解析器:

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

讓我們把它作爲一個旋轉。刪除原始實例Parseable(即Int,Float,當然還有Record),並且只保留單個通用實例。在這裏,我們去:

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

注:這只是一個如何得到使用常規庫通用解析器非常簡單的例子。圖書館本身帶有一個generic list-of-successes parser,記錄特別好。你可能想先檢查一下。此外,該庫還提供了模板Haskell支持,以便可以自動導出Regular的實例。這些實例包括記錄標籤的特殊結構類型,以便您可以讓您的泛型函數將記錄類型處理爲真正的幻想。查看文檔。

3

雖然我很喜歡regular包,我想指出的是,由於ghc-7.2的GHC已經內置了支持獲得通用表示類型,這樣你就不必依賴模板哈斯克爾做到這一點。

與dblhelix建議的解決方案相比的更改如下。你需要稍微不同的標誌和模塊的輸入:

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

import Control.Applicative 
import GHC.Generics 

你仍然定義Parser及其實例如上。 您需要派生類GenericRecord類型:

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

Generic是非常相似的類Regular。您現在不必定義PFRegular的實例。

相反的ParseableF,我們定義一個類Parseable'這是在風格上非常相似,但有一點點不同:

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' 

最後,Parseable,我們定義了一個通用的默認方法:

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 

現在,使得可解析類型爲Record與提供空實例聲明一樣簡單:

instance Parseable Record 

的例子可以作爲前面:

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