3

如何在 Haskell 中组合这些类似的功能?

getGroup [] acc = reverse acc
getGroup ((Letter x):letfs) acc = getGroup letfs ((Letter x):acc)
getGroup ((Group x):letfs) acc = getGroup letfs ((Group (makeList x)):acc)
getGroup ((Star x):letfs) acc = getGroup letfs ((Star (makeList x)):acc)
getGroup ((Plus x):letfs) acc = getGroup letfs ((Plus (makeList x)):acc)
getGroup ((Ques x):letfs) acc = getGroup letfs ((Ques (makeList x)):acc)

Letter、Group、Star、Plus 和 Ques 都是数据类型定义的一部分。

data MyData a 
        = Operand a 
        | Letter a
        | Star [RegEx a] 
        | Plus [RegEx a] 
        | Ques [RegEx a]
        | Pipe [RegEx a] [RegEx a]
        | Group [RegEx a]
        deriving Show

由于它们的相似性,我想知道是否有更好的方法来编写这些函数。大多数情况下,我希望将 Group、Star、Plus 和 Ques 的功能结合起来,因为它们是相同的,但如果有一种方法可以将它们全部结合起来,那就更好了。

4

2 回答 2

4

当您定义了一个数据类型定义时,作为几个不同案例的不相交的联合,您将不可避免地在处理该类型的函数中遇到大量案例分析。

减少案例分析的一种方法是通过分解共性来简化基本类型:

data MyData a = Val String a 
              | UnOp String [Regex a]
              | BinOp String [Regex a] [Regex a]

在这个公式中,每个案例都有一个鉴别器字段,您可以使用它区分每个案例的不同类型。在这里,我只是使用 String 假设你会给它们命名,比如“Operand”、“Letter”、“Star”等,但你也可以为 kind of Val、 kind ofUnOp等的有效鉴别器定义单独的枚举类型。

在这种情况下,您失去的主要是类型安全。String你可以用我给它们的字段来构造特别荒谬的东西。解决这个问题的第一种方法是使用所谓的智能构造函数。这些是具有特定类型参数的函数,它们以类型安全的方式构建更弱类型的核心数据。只要您不MyData从模块中导出实际的构造函数,您类型的其他用户将只能通过您的智能构造函数构造合理的数据。

如果您希望从类型构造函数本身获得更多安全构造的保证,您可能需要求助于广义代数数据类型 (GADT)幻象类型的概念。=这些背后的基本思想是在数据类型定义左侧的类型变量和右侧的类型变量之间建立更灵活的关系。不过,它们是 Haskell 的一个新的和高级的特性,所以你可能想推迟使用它们,直到你牢牢掌握标准的 Haskell 数据类型。

于 2013-08-26T20:21:01.560 回答
4

如果不使用 Template Haskell,就无法摆脱模式匹配的重复,这对于仅五个不同的构造函数来说可能是不值得的。不过,您可以消除很多其他重复,并提高函数的性能特征。

getGroup = map go
  where go (Letter x) = Letter x
        go (Group x) = Group . makeList $ x
        go (Star x) = Star . makeList $ x
        go (Plus x) = Plus . makeList $ x
        go (Ques x) = Ques . makeList $ x

除了更加简洁之外,它还消除了尾递归,这会导致 Haskell 等惰性语言中的空间泄漏。

于 2013-08-26T16:01:31.747 回答