functional-programming - 在 Haskell 函数定义中应用 DRY 的指南

标签 functional-programming dry haskell

我有一个问题,关于应用 DRY 原则的特定方式是否被认为是 Haskell 中的良好实践。我将提供一个示例,然后询问我所采用的方法是否被认为是良好的 Haskell 风格。 简而言之,问题是这样的:当你有一个很长的公式,然后你发现自己需要在别处重复该公式的一些小子集时,你是否总是将这个重复的公式子集放入一个变量中,这样你就可以保持 DRY ?为什么或者为什么不?

示例:
想象一下,我们正在获取一串数字,并将该字符串转换为其相应的 Int 值。 (顺便说一句,这是来自“真实世界 Haskell”的练习)。

这是一个有效的解决方案,只是它忽略了边缘情况:

asInt_fold string = fst (foldr helper (0,0) string)
  where
    helper char (sum,place) = (newValue, newPlace)
      where 
        newValue = (10 ^ place) * (digitToInt char) + sum
        newPlace = place + 1

它使用 foldr,累加器是下一个位置值和到目前为止总和的元组。

到现在为止还挺好。现在,当我开始实现边缘情况检查时,我发现我需要在不同地方使用“newValue”公式的一小部分来检查错误。例如,在我的机器上,如果输入大于 (2^31 - 1),则会出现 Int 溢出,因此我可以处理的最大值是 2,147,483,647。因此,我进行了 2 个检查:
  • 如果位值 9(十亿位)且数字值 > 2,则存在错误。
  • 如果 sum + (10 ^ place) * (digitToInt char) > maxInt,则出现错误。

  • 这 2 个检查使我重复了部分公式,因此我引入了以下新变量:
  • digitValue = digitToInt 字符
  • newPlaceComponent = (10^place) * digitValue

  • 我引入这些变量的原因仅仅是 DRY 原则的自动应用:我发现自己重复了公式的这些部分,所以我定义了它们一次且仅一次。

    但是,我想知道这是否被认为是好的 Haskell 风格。有明显的优点,但我也看到了缺点。它肯定会使代码变长,而我见过的大部分 Haskell 代码都非常简洁。

    那么,您认为这种良好的 Haskell 风格,您是否遵循这种做法?为什么/为什么不?

    对于它的值(value),这是我的最终解决方案,它处理了许多边缘情况,因此有相当大的 where 块。您可以看到由于我应用了 DRY 原则,块变得有多大。

    谢谢。
    asInt_fold "" = error "You can't be giving me an empty string now"
    asInt_fold "-" = error "I need a little more than just a dash"
    asInt_fold string | isInfixOf "." string = error "I can't handle decimal points"
    asInt_fold ('-':xs) = -1 * (asInt_fold xs) 
    asInt_fold string = fst (foldr helper (0,0) string)
      where
        helper char (sum,place) | place == 9 && digitValue > 2 = throwMaxIntError
                   | maxInt - sum < newPlaceComponent      = throwMaxIntError
                       | otherwise                             = (newValue, newPlace)
                where
                  digitValue =  (digitToInt char)
                  placeMultiplier = (10 ^ place)
                  newPlaceComponent = placeMultiplier * digitValue
                  newValue = newPlaceComponent + sum
                  newPlace = place + 1
                  maxInt = 2147483647
                  throwMaxIntError = 
                            error "The value is larger than max, which is 2147483647"
    

    最佳答案

    DRY 在 Haskell 中的原则与其他任何地方一样好:)
    您在 Haskell 中所说的简洁背后的很多原因是,许多习语都被带入了库中,而且您查看的那些示例通常经过仔细考虑以使其简洁:)

    例如,这是实现数字到字符串算法的另一种方法:

    asInt_fold ('-':n) = negate (asInt_fold n)
    asInt_fold "" = error "Need some actual digits!"
    asInt_fold str = foldl' step 0 str
        where
            step _ x
                | x < '0' || x > '9'
                = error "Bad character somewhere!"
            step sum dig =
                case sum * 10 + digitToInt dig of
                    n | n < 0 -> error "Overflow!"
                    n -> n
    

    需要注意的几点:
  • 我们在发生溢出时检测溢出,而不是通过决定我们允许的数字的任意限制。这显着简化了溢出检测逻辑 - 并使其适用于从 Int8 到 Integer 的任何整数类型[只要溢出导致环绕、不发生或导致加法运算符本身的断言]
  • 通过使用不同的折叠,我们不需要两个单独的状态。
  • 不要重复我们自己,即使没有竭尽全力地把事情搞清楚——它自然而然地不是重新陈述我们想要说的话。

  • 现在,并不总是可以只改写算法并使重复消失,但是退后一步重新考虑您一直在考虑的问题总是有用的:)

    关于functional-programming - 在 Haskell 函数定义中应用 DRY 的指南,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/828061/

    相关文章:

    haskell - 在 catamorphism 中组合 f-代数的规则是什么

    python - 检查对象是否是字符串列表的列表?

    java - 重构重写的方法,它们只在主体的中间有区别

    java - 如何使用 Java 流查找数组列总和

    jakarta-ee - 在同一个 Gradle 项目中生成 WAR 和 EAR 工件

    javascript - 在 JavaScript 中仅使用一个按钮保留不同的标题

    haskell - 没有因使用 `print' 而产生 (Show a0) 的实例 类型变量 `a0' 不明确

    Haskell 种类系统与类型族和多参数类型类

    functional-programming - 为什么我的语法会发生变化?

    Java 函数式编程 : How to convert a if-else ladder inside for loop to functional style?