我是Haskell的新手,并且在48小时项目中通过自己编写一个方案开始工作,我遇到了一个实例,我希望从数据类型中获取基础类型,我不知道如何在不为类型中的每个变体编写转换的情况下执行此操作。 例如,在数据类型
中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中退出。
谢谢! 西蒙
答案 0 :(得分:8)
不幸的是,这种关于构造函数的泛型匹配是不可能直接的,但即使它是你的也不行 - extractLispVal
函数没有明确定义的类型,因为类型结果取决于输入的值。有各种类型的高级类型系统废话可以做类似这样的事情,但它们并不是你想要在这里使用的东西。
在您的情况下,如果您只对提取特定类型的值感兴趣,或者您可以将它们转换为单一类型,则可以编写类似extractStringsAndAtoms :: LispVal -> Maybe String
的函数。
返回几种可能类型之一的唯一方法是将它们组合成数据类型和模式匹配 - 这是Either a b
的通用形式,它是a
或{ {1}}由构造函数区分。您可以创建一个允许所有可能类型提取的数据类型......它与b
本身几乎相同,所以这没有帮助。
如果您真的想使用LispVal
之外的各种类型,您还可以查看LispVal
模块,该模块提供了一些反映数据类型的方法。我怀疑这真的是你想要的。
编辑:只是稍微扩展一下,这里有一些你可以编写的提取函数的例子:
创建单构造函数提取函数,如Don的第一个示例所示,假设您已经知道使用了哪个构造函数:
Data.Data
如果应用于extractAtom :: LispVal -> String
extractAtom (Atom a) = a
构造函数之外的其他内容,则会产生运行时错误,因此请谨慎对待。但是,在许多情况下,你知道你在某个算法的某个方面已经得到了什么,所以这可以安全地使用。一个简单的例子就是如果你有一个Atom
的列表,你已经过滤了其他每个构造函数。
创建安全的单构造函数提取函数,它既是“我有这个构造函数吗?”谓词和“如果是这样,给我内容”提取器:
LispVal
请注意,即使您对自己的构造函数有信心,这也比上述更灵活。例如,它使定义这些变得容易:
extractAtom :: LispVal -> Maybe String
extractAtom (Atom a) = Just a
extractAtom _ = Nothing
在定义类型时使用记录语法,如Don的第二个示例中所示。这是一种语言魔力,在大多数情况下,定义了一堆部分函数,如上面的第一个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
,并为您提供了一个用于构造值的精美语法。如果结果是相同类型,您也可以重复使用名称,例如extractAtom
和Atom
。
也就是说,花哨的语法对于包含许多字段的记录更有用,而不是具有许多单字段构造函数的类型,并且上面的安全提取函数通常比产生错误的函数更好。
获得更抽象,有时最方便的方法实际上是拥有一个单一的,通用的解构函数:
String
是的,我知道它看起来很可怕。标准库中的一个示例(在更简单的数据类型上)是函数extractLispVal :: (String -> r) -> ([LispVal] -> r) -> ([LispVal] -> LispVal -> r)
-> (Integer -> r) -> (String -> r) -> (Bool -> r) -> (Double -> r)
-> LispVal -> r
extractLispVal f _ _ _ _ _ _ (Atom x) = f x
extractLispVal _ f _ _ _ _ _ (List xs) = f xs
...
和maybe
,它们解构相同名称的类型。从本质上讲,这是一个功能,它可以激活模式匹配,让您更直接地使用它。它可能很难看,但你只需要写一次,它在某些情况下很有用。例如,您可以使用上述功能执行以下操作:
either
...即一个简单的递归漂亮打印函数,通过如何组合列表的元素进行参数化。您也可以轻松编写exprToString :: ([String] -> String) -> ([String] -> String -> String)
-> LispVal -> String
exprToString f g = extractLispVal id (f . map recur)
(\xs x -> g (map recur xs) $ recur x)
show show show show
where recur = exprToString f g
之类的内容:
isAtom
另一方面,有时您想要做的是匹配一个或两个构造函数,嵌套模式匹配,以及您不关心的构造函数的全包情况。这正是模式匹配最擅长的,而上述所有技术都会让事情变得更加复杂。所以不要只想一种方法!
答案 1 :(得分:6)
您始终可以通过各个构造函数上的模式匹配从数据类型中提取字段:
extractLispValDouble (Double val) = val
或使用记录选择器:
data LispVal = Atom { getAtom :: String }
...
| String { getString :: String }
| Bool { getBool :: Bool }
| Double { getDouble :: Double }
但是,你不能写一个能够天真地返回String或Bool或Double(以及其他任何东西)的函数,因为你不能为它写一个类型。
答案 2 :(得分:2)
使用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" ))