Haskell类型和模式匹配问题:从数据类型中提取字段
我是Haskell的新手,在48小时内完成了“编写自己的方案”项目,我遇到了一个实例,我想从数据类型中获取基础类型,但我不知道如何在不为类型中的每个变量编写转换的情况下实现这一点。 例如,在数据类型中Haskell类型和模式匹配问题:从数据类型中提取字段,haskell,syntax,types,pattern-matching,Haskell,Syntax,Types,Pattern Matching,我是Haskell的新手,在48小时内完成了“编写自己的方案”项目,我遇到了一个实例,我想从数据类型中获取基础类型,但我不知道如何在不为类型中的每个变量编写转换的情况下实现这一点。 例如,在数据类型中 data LispVal = Atom String | List [LispVal] | DottedList [LispVal] LispVal | Number Integer | Str
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| String String
| Bool Bool
| Double Double
我想写这样的东西:(我知道这行不通)
甚至
extractLispVal :: LispVal -> a
extractLispVal (Double val) = val
extractLispVal (Bool val) = val
有可能这样做吗?
基本上,如果需要使用基本类型,我希望能够从LispVal中强制转换出来
谢谢!
Simon您始终可以从数据类型中提取字段,方法是在单个构造函数上进行模式匹配: 或使用记录选择器:
data LispVal = Atom { getAtom :: String }
...
| String { getString :: String }
| Bool { getBool :: Bool }
| Double { getDouble :: Double }
但是,您不能天真地编写返回字符串、Bool或Double(以及其他)的函数,因为您不能为此编写类型。不幸的是,构造函数上的这种泛型匹配无法直接实现,但是,即使它是您的,也无法工作--
extractLispVal
函数没有定义良好的类型,因为结果的类型取决于输入的值。有各种各样的高级类型的系统可以做类似这样的事情,但它们并不是你想要在这里使用的东西
在您的例子中,如果您只对提取特定类型的值感兴趣,或者如果您可以将它们转换为单个类型,那么您可以编写一个函数,例如extractstringsandaoms::LispVal->Maybe String
返回几种可能类型中的一种的唯一方法是将它们组合成一个数据类型,并在该类型上进行模式匹配——其一般形式是a b
,这是由构造函数区分的a
或b
。您可以创建一个允许所有可能类型提取的数据类型。。。它与LispVal
本身几乎相同,所以这没有什么帮助
如果您确实想使用LispVal
之外的各种类型,还可以查看Data.Data
模块,该模块提供了一些反映数据类型的方法。不过,我怀疑这是你真正想要的
编辑:这里是一些您可以编写的提取函数示例:
- 创建单个构造函数提取函数,如Don的第一个示例中所示,假设您已经知道使用了哪个构造函数:
如果应用于非extractAtom :: LispVal -> String extractAtom (Atom a) = a
构造函数,则会产生运行时错误,因此要小心。不过,在很多情况下,通过在算法中的某个点,你知道你得到了什么,所以这可以安全地使用。一个简单的例子是,如果你有一个Atom
s列表,你已经过滤掉了所有其他构造函数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
和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 ...
是的,看起来很可怕,我知道。标准库中的一个例子是函数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
和maybe
,它们解构了相同名称的类型。本质上,这是一个具体化模式匹配的函数,可以让您更直接地使用它。它可能很难看,但你只需要写一次,在某些情况下它可能很有用。例如,使用上述函数可以做一件事:other
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 ...
…即,一个简单的递归漂亮打印函数,通过如何组合列表元素进行参数化。您还可以轻松编写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
isAtom = extractLispVal (const True) no (const no) no no no no where no = const False
- 另一方面,有时您要做的是使用嵌套模式匹配来匹配一个或两个构造函数,并为您不关心的构造函数提供一个全面的情况。这正是模式匹配最擅长的,所有上述技术只会让事情变得更加复杂。所以,不要只局限于一种方法
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" ))
谢谢,这就是我想的,我只是想确定一下。哈斯凯尔的打字系统对我来说仍然有点神秘,所以我想这里可能会发生某种魔法:)@Simon:嗯,也许会。如果您发现您的代码冗长或笨拙,并且希望使用一个有趣的工具来简化它