3
\$\begingroup\$

Trying to get to grips with Haskell. The problem is taken from here.

{-
Given Credit Card number: 49927398716 
• Reverse the digits: 61789372994 
• Sum the odd digits: 6 +たす 7 +たす 9 +たす 7 +たす 9 +たす 4 = 42 = s1 
• The even digits: 1, 8, 3, 2, 9 
• Two times each even digit: 2, 16, 6, 4, 18 
• Sum the digits of each multiplication: 2, 7, 6, 4, 9 
• Sum the last: 2 +たす 7 +たす 6 +たす 4 +たす 9 = 28 = s2
• s1 + s2 = 70 
• which, as it ends in zero, means that 49927398716 passes the Luhn test
-}
import Data.Char
data Validity = Valid | Invalid deriving Eq
type CardNumber = String
luhnCheck :: CardNumber -> Validity
luhnCheck s = 
 let r = revDigits s
 (os,es) = oddsAndEvens r
 s1 = sum os
 s2 = sum $ map sumDigits $ map (*2) es 
 in if (s1 + s2) `mod` 10 == 0 then Valid
 else Invalid
revDigits :: CardNumber -> [Int]
revDigits s = map digitToInt (reverse s) 
oddsAndEvens :: [Int] -> ([Int],[Int])
oddsAndEvens is = 
 let digitsWithIdx = zip is (cycle [1,2])
 odds = map fst $ filter (\d -> (snd d) == 1) digitsWithIdx
 evens = map fst $ filter (\d -> (snd d) == 2) digitsWithIdx
 in (odds,evens) 
sumDigits:: Int -> Int
sumDigits i = sum $ map digitToInt $ show i
test :: Bool
test = let testData = ["49927398716", "49927398717", "1234567812345678","1234567812345670"]
 in (map luhnCheck testData) == [Valid,Invalid,Invalid,Valid]
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Feb 18, 2015 at 9:29
\$\endgroup\$
1

1 Answer 1

2
\$\begingroup\$

One thing that immediately pops out (and happens a lot with beginner Haskell programmers, IME):

s2 = sum $ map sumDigits $ map (*2) es 

This means (reads as for me) "map (*2) on es, then map sumDigits on it, then sum the numbers."

What you typically want to do is to stay on the "function side" on things as long as possible:

s2 = sum . map sumDigits . map (*2) $ es 

Meaning you compose as much as you can, and put only one application at the end. That makes it easier to eta-reduce and in general is considered more idiomatic.


You could also compose the mapped operation, bringing it down to classic "map-reduce" scenario (where "map" part is sumDigits . (*2), and "reduce" part is the sum).

s2 = sum . map (sumDigits . (*2)) $ es 

I'd also flip the order of Valid and Invalid:

data Validity = Invalid | Valid deriving Eq

This way, when you derive Ord, Valid > Invalid, which is sometimes helpful and in general you'll see that it holds "intuitively", for Maybe (Just _ > Nothing), Either (Right _ > Left _) and similar values.

answered Feb 18, 2015 at 9:48
\$\endgroup\$
2
  • \$\begingroup\$ I guess the function composition comment would apply to the oddsAndEvens function too? \$\endgroup\$ Commented Feb 19, 2015 at 9:19
  • \$\begingroup\$ @jb77 Yep. It applies generally. \$\endgroup\$ Commented Feb 19, 2015 at 9:41

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.