簡體   English   中英

Haskell類型和模式匹配問題:從數據類型中提取字段

[英]Haskell type and pattern matching question: extracting fields from a data type

我是Haskell的新手,在48小時項目中通過Write Yourself a Scheme開始工作,我遇到了一個實例,我希望從數據類型中獲取基礎類型,我不知道如何在沒有為類型中的每個變體編寫轉換。 例如,在數據類型中

data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | String String
             | Bool Bool
             | Double Double

我想寫一些類似的東西:(我知道這不起作用)

extractLispVal :: LispVal -> a
extractLispVal (a val) = val

甚至

extractLispVal :: LispVal -> a
extractLispVal (Double val) = val
extractLispVal (Bool val) = val

是否有可能做到這一點? 基本上,如果我需要使用基本類型,我希望能夠從LispVal中退出。

謝謝! 西蒙

不幸的是,構造函數上的這種泛型匹配是不可能直接的,但即使它是你的也不行 - extractLispVal函數沒有明確定義的類型,因為結果的類型取決於值輸入。 有各種類型的高級類型系統廢話可以做這樣的事情,但它們並不是你想要在這里使用的東西。

在您的情況下,如果您只對提取特定類型的值感興趣,或者您可以將它們轉換為單個類型,則可以編寫類似extractStringsAndAtoms :: LispVal -> Maybe String的函數。

返回幾種可能類型中的一種的唯一方法是將它們組合成數據類型和模式匹配 - 這是它的通用形式, Either ab ,它是由構造函數區分的ab 您可以創建一個允許所有可能類型提取的數據類型......它與LispVal本身幾乎相同,所以這沒有用。

如果你真的想在LispVal之外使用LispVal你也可以查看Data.Data模塊,它提供了一些反映數據類型的方法。 不過,我懷疑這真的是你想要的。


編輯 :只是為了擴展一些東西,這里有一些你可以編寫的提取函數的例子:

  • 創建單構造函數提取函數,如Don的第一個示例,假設您已經知道使用了哪個構造函數:

     extractAtom :: LispVal -> String extractAtom (Atom a) = a 

    如果應用於Atom構造函數以外的其他內容,這將產生運行時錯誤,因此請謹慎。 但是,在許多情況下,你知道你在某個算法的某個方面已經得到了什么,所以這可以安全地使用。 一個簡單的例子就是如果你有一個LispVal列表,你已經過濾了其他每個構造函數。

  • 創建安全的單構造函數提取函數,它既可以作為“我有這個構造函數嗎?” 謂詞和“如果是這樣,給我內容”提取器:

     extractAtom :: LispVal -> Maybe String extractAtom (Atom a) = Just a extractAtom _ = Nothing 

    請注意,即使您對自己的構造函數有信心,這也比上述更靈活。 例如,它使定義這些變得容易:

     isAtom :: LispVal -> Bool isAtom = isJust . extractAtom assumeAtom :: LispVal -> String assumeAtom x = case extractAtom x of Just a -> a Nothing -> error $ "assumeAtom applied to " ++ show x 
  • 在定義類型時使用記錄語法,如Don的第二個示例中所示。 這是一種語言魔力,在大多數情況下,定義了一堆部分函數,​​如上面的第一個extractAtom ,並為您提供了一個用於構造值的精美語法。 如果結果是相同的類型,您也可以重用名稱,例如AtomString

    也就是說,花哨的語法對於具有許多字段的記錄更有用,而不是具有許多單字段構造函數的類型,並且上面的安全提取函數通常比產生錯誤的函數更好。

  • 獲得更抽象,有時最方便的方法實際上是擁有一個單一的,通用的解構函數:

     extractLispVal :: (String -> r) -> ([LispVal] -> r) -> ([LispVal] -> LispVal -> r) -> (Integer -> r) -> (String -> r) -> (Bool -> r) -> (Double -> r) -> LispVal -> r extractLispVal f _ _ _ _ _ _ (Atom x) = fx extractLispVal _ f _ _ _ _ _ (List xs) = f xs ... 

    是的,我知道它看起來很可怕。 的這(在更簡單的數據類型)中的標准庫的一個例子是函數maybeeither ,其中解構類型相同的名稱的。 從本質上講,這是一個功能,它可以激活模式匹配,讓您更直接地使用它。 它可能很難看,但你只需要寫一次,它在某些情況下很有用。 例如,您可以使用上述功能執行以下操作:

     exprToString :: ([String] -> String) -> ([String] -> String -> String) -> LispVal -> String exprToString fg = extractLispVal id (f . map recur) (\\xs x -> g (map recur xs) $ recur x) show show show show where recur = exprToString fg 

    ...即,一個簡單的遞歸漂亮打印功能,通過如何組合列表的元素進行參數化。 您還可以輕松地編寫isAtom等:

     isAtom = extractLispVal (const True) no (const no) no no no no where no = const False 
  • 另一方面,有時您想要做的是匹配一個或兩個構造函數,嵌套模式匹配,以及您不關心的構造函數的全包情況。 這正是模式匹配最擅長的,而上述所有技術都會讓事情變得更加復雜。 所以不要只想一種方法!

您始終可以通過單個構造函數上的模式匹配從數據類型中提取字段:

extractLispValDouble (Double val) = val

或使用記錄選擇器:

data LispVal = Atom { getAtom :: String }
             ...          
             | String { getString :: String }
             | Bool   { getBool :: Bool }
             | Double { getDouble :: Double }

但是,你不能編寫一個天真地返回String或Bool或Double(以及其他任何東西)的函數,因為你不能為它寫一個類型。

您可以使用GADT獲得或多或少的結果。 它很快就會變得嚇人,但它確實有效:-)但我懷疑這種方法能給你帶來多大的幫助!

這是我快速掀起的東西,有一個不太正確(有點太多的空格) printLispVal函數拋出 - 我寫了一下,看看你是否真的可以使用我的構造。 請注意,提取基本類型的樣板文件位於extractShowableLispVal函數中。 我認為當你開始做更復雜的事情,比如嘗試做算術時,這種方法會很快遇到麻煩。

{-# LANGUAGE GADTs #-}
data Unknown = Unknown

data LispList where
    Nil :: LispList
    Cons :: LispVal a -> LispList -> LispList

data LispVal t where
    Atom :: String -> LispVal Unknown
    List :: LispList -> LispVal Unknown
    DottedList :: LispList -> LispVal b -> LispVal Unknown
    Number :: Integer -> LispVal Integer
    String :: String -> LispVal String
    Bool   :: Bool -> LispVal Bool
    Double :: Double -> LispVal Double

data Showable s where
    Showable :: Show s => s -> Showable s

extractShowableLispVal :: LispVal a -> Maybe (Showable a)
extractShowableLispVal (Number x) = Just (Showable x)
extractShowableLispVal (String x) = Just (Showable x)
extractShowableLispVal (Bool x) = Just (Showable x)
extractShowableLispVal (Double x) = Just (Showable x)
extractShowableLispVal _ = Nothing

extractBasicLispVal :: LispVal a -> Maybe a
extractBasicLispVal x = case extractShowableLispVal x of
    Just (Showable s) -> Just s
    Nothing -> Nothing

printLispVal :: LispVal a -> IO ()
printLispVal x = case extractShowableLispVal x of    
    Just (Showable s) -> putStr (show s)
    Nothing -> case x of
        Atom a -> putStr a
        List l -> putChar '(' >> printLispListNoOpen (return ()) l
        DottedList l x -> putChar '(' >> printLispListNoOpen (putChar '.' >> printLispVal x) l

printLispListNoOpen finish = worker where
    worker Nil = finish >> putChar ')'
    worker (Cons car cdr) = printLispVal car >> putChar ' ' >> worker cdr

test = List . Cons (Atom "+") . Cons (Number 3) . Cons (String "foo") $ Nil
test2 = DottedList (Cons (Atom "+") . Cons (Number 3) . Cons (String "foo") $ Nil) test
-- printLispVal test prints out (+ 3 "foo" )
-- printLispVal test2 prints out (+ 3 "foo" .(+ 3 "foo" ))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM