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