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"