This is a good start! I'll do two passes over your code, one to address issues of style, and another to leverage functions from the Haskell Prelude to decrease code length and bring it more in line with typical Haskell usage and idioms.
sumOfChars function is a good example of tail-recursion, but in Haskell we would take that
String apart through pattern matching. A Haskell
String is really a list of characters, or
[Char], so we'll decompose it as a list.
sumOfChars :: [Char] -> Int -- String is a synonym for [Char] sumOfChars  = 0 -- The empty list is the same value as the empty string, i.e.  == "" sumOfChars (c:cs) = digitToInt c + sumOfChars cs
: is the list cons operator,
c is the head of the list and
cs is the tail. So,
c == str !! 0 and
cs == tail str. (And
head is the function we usually use for getting the element at index 0 in a list.)
twoMult isn't wrong, but function application in Haskell doesn't require any parentheses. Parentheses are for grouping since function application has the highest precedence of any operation.
twoMult :: Char -> String twoMult c = show (digitToInt c * 2)
if statement at the top level of a function is usually an indication that you can use a guard. Guards are a bit like a multi-way
identificationSum :: [Char] -> Int identificationSum  = 0 identificationSum s@(c:cs) -- The 's@' part is an as-pattern, explained below | length s `mod` 2 == 1 = sumOfChars (twoMult c) + identificationSum cs | otherwise = digitToInt c + identificationSum cs
Each guard begins with a pipe (
|) and evaluates to a
Bool value. First pattern matching takes place, then guards are evaluated in order.
otherwise is just a synonym for
True, i.e., a guard that always succeeds when evaluation reaches it.
I used an as-pattern there to bind the value of
c:cs to an identifier. This is a handy shortcut and much preferable to writing
c:cs all over the place if you need both the original value and its decomposition.
remainingToTens is a little odd, and I'd guess that you got to that answer when the compiler complained about there not being an instance for
Fractional Int, yeah? Integer division uses a function called
/ is for
Fractional values (like
Double) (yeah it's a little strange the first time you come across this).
remainingToTens :: Int -> Int remainingToTens x = (x `div` 10 + 1) * 10 - x -- Edit: This is incorrect, use version below
determineLastDigit just has a few extra parentheses.
determineLastDigit :: String -> Int determineLastDigit s = remainingToTens (identificationSum s)
Many of the operations you implemented can be expressed using some of the higher-order functions we have in the Prelude and coding in a functional style.
Instead of using primitive recursion on the elements of a list in
sumOfChars, typical Haskell usage would see us using functions like
map to manipulate the entire list without getting into the weeds.
sumOfChars :: [Char] -> Int sumOfChars cs = sum (map digitToInt cs)
maps type is
(a -> b) -> [a] -> [b], that is, it takes a function - that itself takes an element of type
a and returns something of type
b - and a list and returns a list where that function has been applied to every element of the first list.
sum adds all the values in a list of numbers.
One further thing we could do to that function is to write it in pointfree style. Writing functions pointfree is definitely an aspect of Haskell style, but don't concentrate on it overmuch until you have a solid grasp on the basics of the language. I've included an Appendix A at the bottom of this post where functions have been written pointfree for your reference.
identificationSum could be written many, many ways (some as in the appendices below) but given the context of
twoMult, and the specification of the algorithm I would favor separating out the functionality into two phases. First, constructing a new string based on doubling every other digit, and secondly summing all of the digits. To do this I'll introduce a new function.
doubleAlternating :: [Char] -> [Char] doubleAlternating  =  doubleAlternating (c:) = twoMult c doubleAlternating (c:d:cs) = twoMult c ++ [d] ++ doubleAlternating cs
identificationSum is a composition of
identificationSum :: String -> Int identificationSum s = sumOfChars (doubleAlternating s)
remainingToTens could really just benefit from some modular arithmetic. Use the
remainingToTens :: Int -> Int remainingToTens x = negate x `mod` 10
I'll take it a little further with inlining in the appendices, but as it stands this is a very readable translation of the problem as it was stated. Having a close correspondence to the problem domain can be much more valuable than terse code for the sake of terseness.
Appendix A: Pointfree
One of the things I like about pointfree style is that it forces you to think in terms of functions, higher order abstractions, and data pipelines. Presented without further comment.
sumOfChars :: [Char] -> Int sumOfChars = sum . map digitToInt twoMult :: Char -> String twoMult = show . (* 2) . digitToInt identificationSum :: String -> Int identificationSum = sumOfChars . doubleAlternating determineLastDigit :: String -> Int determineLastDigit = remainingToTens . identificationSum
Appendix B: Code golf
Understanding this will help you explore the Prelude and hopefully have a stronger grasp of function composition. I'll admit it's a little showy though. ;-)
checksum :: String -> Int checksum s = negate (sum . map digitToInt . concatMap show . zipWith ($) (cycle [(* 2), id]) . map digitToInt $ s) `mod` 10