Haskell类型和模式匹配问题:从数据类型中提取字段 [英] Haskell type and pattern matching question: extracting fields from a data type

查看:119
本文介绍了Haskell类型和模式匹配问题:从数据类型中提取字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Haskell的新手,在48小时项目中编写自己的Scheme,我遇到了一个想要从数据类型中获取底层类型的实例,我不确定如何不必为类型中的每个变体写入转换。
例如,在数据类型中

  data LispVal = Atom String 
|列表[LispVal]
| DottedList [LispVal] LispVal
|数字整数
|字符串字符串
| Bool Bool
| Double Double

我想写下如下内容:(我知道这行不通)

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

甚至

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

是否有可能做到这一点?
基本上我希望能够退出LispVal,如果我需要使用基本类型的话。



谢谢!
Simon

解决方案

不幸的是,这种类型的构造函数通用匹配是不可能的,但即使它是你的将不起作用 - extractLispVal 函数没有一个定义良好的类型,因为结果的类型取决于输入的值。有各种各样的先进的类型系统废话可以做这样的事情,但他们不是真的想要在这里使用的东西。



在你的情况下,如果你只想提取特定类型的值,或者如果你可以将它们转换为单一类型,你可以编写一个函数,如 extractStringsAndAtoms :: LispVal - >例如,可能是String

返回几种可能类型之一的唯一方法是将它们组合成数据类型和模式匹配 - 这是<$ c $的通用形式c> ab ,它是由构造函数区分的 a b 。你可以创建一个允许所有可能的类型提取的数据类型......并且它与 LispVal 本身几乎相同,所以这没有帮助。



如果您真的想在 LispVal 之外使用各种类型,您还可以查看 Data.Data 模块,它提供了一些反映数据类型的方法。尽管如此,我怀疑这确实是你想要的。






编辑:下面是一些你可以编写的提取函数的例子:


  • 创建单构造函数提取函数,如Don的第一个函数例如,假设您已经知道使用了哪个构造函数:

      extractAtom :: LispVal  - >字符串
    extractAtom(Atom a)= a

    如果应用于某些东西除了 Atom 构造函数之外,所以要谨慎。然而,在很多情况下,你知道在算法的某个时刻你已经有了什么,所以这可以安全地使用。一个简单的例子就是如果你有一个 LispVal s列表,你已经过滤了其他所有构造函数。


  • 创建安全的单构造函数提取函数,它既是我有这个构造函数吗?谓词和如果是这样,给我的内容提取:

      extractAtom :: LispVal  - > Maybe String 
    extractAtom(Atom a)=只是一个
    extractAtom _ = Nothing

    请注意,即使您对自己的构造函数有信心,它也比上述更灵活。例如,它使得定义这些变得容易:

      isAtom :: LispVal  - > Bool 
    isAtom = isJust。 extractAtom

    assumeAtom :: LispVal - > String
    assumeAtom x = case extractAtom x of
    只是一个 - > a
    Nothing - >错误$assumeAtom应用于++显示x


  • 定义类型时使用记录语法,正如唐的第二个例子。这在语言方面有点神奇,大多数情况下,它定义了大量的部分函数,​​如上面的第一个 extractAtom ,并为您提供了构建值的一种奇妙语法。如果结果是相同的类型,您也可以重用名称,例如为 Atom 字符串



    也就是说,花式语法对于包含许多字段的记录更有用,而不是包含许多单字段构造函数的类型,上面的安全提取函数通常比那些产生错误的函数要好。


  •   extractLispVal提取抽象,有时最简单的方法实际上是一个单一的,多用途的解构函数。 ::(String  - > r) - > ([LispVal]  - > r) - > ([LispVal]  - > LispVal  - > r)
    - > (整数 - > r) - > (String - > r) - > (Bool→> r)→> (Double - > r)
    - > LispVal - > r
    extractLispVal f _ _ _ _ _ _(Atom x)= fx
    extractLispVal _ f _ _ _ _ _(List xs)= f xs
    ...

    是的,我知道它看起来很可怕。标准库中的一个例子(在一个简单的数据类型上)是函数也许或者,解构器相同名称的类型。实质上,这是一个函数,它可以使模式匹配发生变化,并让您更直接地使用它。它可能很难看,但你只需要写一次,在某些情况下它可能很有用。例如,你可以用上面的函数做一件事:

      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
    where recur = exprToString fg

    ...即一个简单的递归漂亮打印函数,结合列表的元素。您也可以轻松地写 isAtom 之类的:

      isAtom = extractLispVal (const True)no(const no)no no no no 
    where no = const False




I'm new to Haskell and working my way through the Write Yourself a Scheme in 48 Hours project and I came upon an instance where I wanted to get the underlying type out of a data type and I'm not sure how to do it without writing conversions for each variant in the type. For example, in the data type

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

I want to write something like: (I know this doesn't work)

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

or even

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

Is it possible to do this? Basically I want to be able to cast back out of the LispVal if I need to use the basic type.

Thanks! Simon

解决方案

Unfortunately this sort of generic matching on constructors isn't possible directly, but even if it was yours wouldn't work--the extractLispVal function doesn't have a well-defined type, because the type of the result depends on the value of the input. There are various kinds of advanced type-system nonsense that can do things sort of like this, but they're not really something you'd want to use here anyway.

In your case, if you're only interested in extracting particular kinds of values, or if you can convert them to a single type, you can write a function like extractStringsAndAtoms :: LispVal -> Maybe String, for instance.

The only way to return one of several possible types is by combining them into a data type and pattern matching on that--the generic form of this being Either a b, which is either a or b distinguished by constructors. You could create a data type that would permit all possible types to extract... and it would be pretty much the same as LispVal itself, so that's not helpful.

If you really want to work with various types outside of a LispVal you could also look at the Data.Data module, which provides some means for reflection on data types. I doubt that's really what you want, though.


EDIT: Just to expand on things a bit, here's a few examples of extraction functions you can write:

  • Create single-constructor extraction functions, as in Don's first example, that assume you already know which constructor was used:

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

    This will produce runtime errors if applied to something other than the Atom constructor, so be cautious with that. In many cases, though, you know by virtue of being at some point in an algorithm what you've got, so this can be used safely. A simple example would be if you've got a list of LispVals that you've filtered every other constructor out of.

  • Create safe single-constructor extraction functions, which serve as both a "do I have this constructor?" predicate and an "if so, give me the contents" extractor:

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

    Note that this is more flexible than the above, even if you're confident of what constructor you have. For example, it makes defining these easy:

    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
    

  • Use record syntax when defining the type, as in Don's second example. This is a bit of language magic, for the most part, defines a bunch of partial functions like the first extractAtom above and gives you a fancy syntax for constructing values. You can also reuse names if the result is the same type, e.g. for Atom and String.

    That said, the fancy syntax is more useful for records with many fields, not types with many single-field constructors, and the safe extraction functions above are generally better than ones that produce errors.

  • Getting more abstract, sometimes the most convenient way is actually to have a single, all-purpose deconstruction function:

    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
    ...
    

    Yeah, it looks horrendous, I know. An example of this (on a simpler data type) in the standard libraries are the functions maybe and either, which deconstruct the types of the same names. Essentially, this is a function that reifies the pattern matching and lets you work with that more directly. It may be ugly, but you only have to write it once, and it can be useful in some situations. For instance, here's one thing you could do with the above function:

    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
    

    ...i.e., A simple recursive pretty-printing function, parameterized by how to combine the elements of a list. You can also write isAtom and the like easily:

    isAtom = extractLispVal (const True) no (const no) no no no no
      where no = const False
    

  • On the other hand, sometimes what you want to do is match one or two constructors, with nested pattern matches, and a catch-all case for the constructors you don't care about. This is exactly what pattern matching is best at, and all the above techniques would just make things far more complicated. So don't tie yourself to just one approach!

这篇关于Haskell类型和模式匹配问题:从数据类型中提取字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆