昨日の記事のdigit(桁数)関数のバグを修正
ガードにおいてはotherwiseのような自明な全捕捉でない限り
コンパイラーは条件が網羅的かどうか推論できないらしいということを学んだ.
それはそれとして, 昨日書いたdigitにはバグがあることに気づいた*1.
符号付き整数に関して,
let m = (minBound :: Int) in m == (-1) * m --> True
が成り立つので, digit (minBound :: Int) は無限ループに陥ってしまう.
回避の仕方はいくつかあると思うけど,
(minBound :: a) が-10^n の格好で与えられるような型aが存在する場合にも
対応しようと思うと, divを用いて求める方法が安全と思われる.
すなわち, 10で割った数に対して digitを求め, それに1を加えて答えとする.
digit :: (Integral a) => a -> a digit n | n > 0 = (+1) . floor . (logBase 10) $ (fromIntegral n :: Float) | n == 0 = 1 | n == negate n = (+1) . digit . abs $ n `div` 10 | otherwise = digit $ abs n
通常は符号付き整数の下限は -2^n (n>0)の形で与えられるはずだから,
その10進表記の末尾は2,4,6,8のいずれかになるので,
1を足しても繰り下がりはせず,
digit (-2^n) == digit (-2^n +1)
が成り立つはずである. よって以下の定義でも普通は問題ないような気がする.
| n == negate n = digit . abs $ n + 1
あとガードの順序は割と重要.
negate 0 == 0 だし, あと符号なし整数に対しては例えば
import Data.Word (Word8) negate (128 :: Word8) == 128 --> True
が成立してしまうので, この条件判定は非負の整数に適用されるとまずい.
あるいはおとなしく文字列の長さを数えるか…
import Data.List (genericLength) digit :: (Show a, Integral a) => a -> a digit = genericLength . (dropWhile (=='-')) . show
*1:DateCreateで使う分には関係ない部分の話ではある.