在Haskell中组合函数

时间:2013-08-26 15:04:58

标签: function haskell functional-programming

如何在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的功能结合起来,因为它们是相同的,但是如果有一种方法可以将所有这些功能结合起来,那就更好了。

2 个答案:

答案 0 :(得分:4)

如果不使用模板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这样的惰性语言中的空间泄漏。

答案 1 :(得分:4)

如果您定义了一个数据类型定义,作为几个不同情况的不相交联合,您将不可避免地在处理该类型的函数中结束大量案例分析。

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

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

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

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

如果您希望从类型构造函数本身获得更多安全构造的保证,您可能需要转向广义代数数据类型(GADT)幻像类型的概念。这些背后的基本思想是在数据类型定义的=左侧的类型变量与右侧的类型变量之间建立更灵活的关系。然而,它们是Haskell的一个新的高级功能,所以你可能想要继续跳进它们,直到你牢牢掌握标准的Haskell数据类型。