【问题标题】:Combining functions in Haskell在 Haskell 中组合函数
【发布时间】:2013-08-26 15:04:58
【问题描述】:

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

【问题讨论】:

    标签: function haskell functional-programming


    【解决方案1】:

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

    【讨论】:

    • 对不起...我是 Haskell 的新手。所以我的函数 Letter(例如)实际上会变成:go ((Letter x):lefts) acc = getGroup lefts ((Letter x):acc) ?
    • 不,去掉蓄能器。尾递归在处理惰性求值时很糟糕;它会导致空间泄漏。最好使用像map这样的高阶函数,但是如果你要使用显式递归,它应该是这样的getGroup ((Letter x):lefts) = Letter x : getGroup lefts
    • 我收到此错误:parse error on input go', which is occuring at the second go: getGroup = map go where go ((Letter x):lefts) = Letter x : getGroup lefts go ((Star x): lefts) = (Star .makeList $x) : getGroup lefts go ((Plus x):lefts) = (Plus .makeList $ x) : getGroup lefts go ((Ques x):lefts) = (Ques .makeList $x) : getGroup lefts go ((Group x):lefts) = (Group .makeList $ x) : getGroup lefts`(所以我想第一个没问题)。
    • 另外,使用显式递归似乎可行 :D,但它仍然分为五个函数。
    • 尝试逐字复制答案并从回复中手动复制它应该可以工作。您的go 定义与答案中的不同。
    【解决方案2】:

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

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

    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 数据类型之前,您可能希望暂缓使用它们。

    【讨论】:

      猜你喜欢
      • 2016-04-30
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-26
      • 1970-01-01
      相关资源
      最近更新 更多