Best Practices for Using DRY in Haskell Function Definitions

My question is whether any particular way of applying the DRY principle is considered good practice in Haskell. I will give an example and then ask if the approach that I take is considered to be a good Haskell style. In a nutshell, the question is: when you have a long formula, and then you need to repeat some small subsets of this formula elsewhere, do you always put this repeating subset of the formula in a variable so that you can remain DRY? Why or why not?

Example: Imagine we take a string of numbers and convert this string to the corresponding Int value. (BTW, this is an exercise from "Real World Haskell").

Here's a solution that works, except that it ignores edge cases:

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

It uses foldr, and the battery is a tuple of the next place and amount so far.

So far so good. Now that I went to implement edge checks, I found that I needed small parts of the "newValue" formula in different places to check for errors. For example, on my machine there would be an Int overflow if the input was larger than (2 ^ 31 - 1), so the maximum value that I could handle is 2,147,483,647. So I put 2 checks:

  • If the location value is 9 (billionth place) and the digital value is> 2, an error appears.
  • If the sum is + (10 ^ place) * (digitToInt char)> maxInt, there is an error.

Those 2 checks made me repeat part of the formula, so I introduced the following new variables:

  • digitValue = digitToInt char
  • newPlaceComponent = (10 ^ ) * digitValue

, , DRY: , , .

, , Haskell. , . , Haskell, , .

, Haskell, ? / ?

, , . , - 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"
+3
3

bdonlan, - , . , , , . , .

placeMultiplier, , place ^ 10 . maxInt Prelude, , 64- . , , - . , .

( : 10 000-20 000 Haskell, , , . ML, .)

+4

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 [ , , )
  • , .
  • , , - , .

, , :)

+9

, , , .

, , . , , . , , , - .

, hardcoding Int (maxBound:: Int), Int, .

+2

Source: https://habr.com/ru/post/1707735/


All Articles