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