正如 Paul Johnson 在评论中提到的,手头的第一个任务是从增值税号中获取十进制数字列表。
也许将计算数字的逻辑与计算 r 和 s 的逻辑交织在一起并不是一个好主意。生成的代码可能难以调试。
让我们尝试使用ghci 解释器以交互方式开发代码。
获取十进制数字列表:
为此,我们可以先作弊,只使用Show 实例作为整数。
$ ghci
GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help
λ>
λ> vat1 = 502618411
λ>
λ> vat1
502618411
λ>
λ> show vat1
"502618411"
λ>
但在 Haskell 中,String 只是 Char 的列表。所以这与:['5', '0', '2', '6', '1', '8', '4', '1', '1'] 相同。
λ>
λ> show vat1 == ['5', '0', '2', '6', '1', '8', '4', '1', '1']
True
λ>
我们可以使用Data.Char模块中的ord函数从字符中获取数字ASCII代码:
λ>
λ> import qualified Data.Char as Ch
λ> :type Ch.ord
Ch.ord :: Char -> Int
λ>
λ> map Ch.ord (show vat1)
[53,48,50,54,49,56,52,49,49]
λ>
由于 0 到 9 的数字在 ASCII 序列中连续出现(从 48 开始),我们通过减去字符 '0' 的代码得到数字:
λ>
λ> map (\c -> (Ch.ord c) - (Ch.ord '0')) (show vat1)
[5,0,2,6,1,8,4,1,1]
λ>
所以我们有我们的数字列表。让我们定义函数:
λ>
λ> getDigits vat = map (\ch -> (Ch.ord ch) - (Ch.ord '0')) (show vat)
λ>
λ> getDigits vat1
[5,0,2,6,1,8,4,1,1]
λ>
但如果我们不想使用 Show 实例怎么办?
然后我们可以使用纯数字递归函数。
让我们将已经生成的数字列表(例如dgs)作为累加器。使用ghci 中的多行功能:
λ>
λ> :{
|λ> getDigitsR dgs n = let (q,r) = (divMod n 10)
|λ> in if (q==0) then (r : dgs)
|λ> else getDigitsR (r : dgs) q
|λ>
|λ> :}
λ>
测试:
λ>
λ> getDigitsR [9,9,9] vat1
[5,0,2,6,1,8,4,1,1,9,9,9]
λ>
λ> getDigitsR [] vat1
[5,0,2,6,1,8,4,1,1]
λ>
所以我们可以为getDigits 编写替代定义:
λ>
λ> getDigits vat = getDigitsR [] vat
λ>
计算r和s:
根据规范,一个数字的权重从 9 开始,每一步减 1。低于 2 的权重,求和停止。这导致以下递归定义用于从增值税号的数字计算s:
λ>
λ> :{
|λ> getSr w [] = 0
|λ> getSr w (dg:dgs) = if (w < 2) then 0 else (w*dg + getSr (w-1) dgs)
|λ> :}
λ>
λ>
λ> digits1
[5,0,2,6,1,8,4,1,1]
λ>
λ> getSr 9 digits1
146
λ>
这导致了计算 s 的这个简单函数:
λ>
λ> getS vat = getSr 9 (getDigits vat)
λ>
但如果首选使用库的样式怎么办?
人们普遍认为,随着 Haskell 程序员获得更多经验,他们倾向于使用更少的直接手动递归和更多的内部递归库函数。
如果我们想在这里这样做,我们需要压缩权重和数字:
λ>
λ> ws = [9,8,7,6,5,4,3,2]
λ> digits = getDigits vat1
λ>
λ> zip ws digits
[(9,5),(8,0),(7,2),(6,6),(5,1),(4,8),(3,4),(2,1)]
λ>
此时我们可以进行乘法运算并对结果求和:
λ>
λ> map (\(w,d) -> w*d) (zip ws digits)
[45,0,14,36,5,32,12,2]
λ>
λ> sum $ map (\(w,d) -> w*d) (zip ws digits)
146
λ>
库函数zipWith 使简化成为可能:
λ>
λ> sum $ zipWith (*) ws digits
146
λ>
所以我们可以编写 getS 函数的替代版本:
λ>
λ> :{
|λ> getS vat = let ws = [9,8,7,6,5,4,3,2]
|λ> digits = getDigits vat
|λ> in sum $ zipWith (*) ws digits
|λ> :}
λ>
λ> getS vat1
146
λ>
请注意,这种样式可以更轻松地处理任意权重。
结论:
因此我们的工作基本上完成了:
λ>
λ> vat1
502618411
λ> getS vat1
146
λ> r = mod 146 11
λ> r
3
λ>
通过选择样式并提供类型签名将代码放入最终形状,留给读者作为练习。