Сегодня я нашел этот пост в Quora, в котором утверждалось, что
factorial(n) = def $ do
assert (n<=0) "Negative factorial"
ret <- var 1
i <- var n
while i $ do
ret *= i
i -= 1
return ret
может быть правильным кодом Haskell. Мне стало любопытно, и закончил с
factorial :: Integer -> Integer
factorial n = def $ do
assert (n >= 0) "Negative factorial"
ret <- var 1
i <- var n
while i $ do
ret *= i
i -= 1
return ret
используя var = newSTRef
, канонические определения для def
, assert
и while
и
a *= b = readSTRef b >>= \b -> modifySTRef a ((*) b)
a -= b = modifySTRef a ((+) (negate b))
Однако (*=)
и (-=)
имеют разные типы:
(-=) :: Num a => STRef s a -> a -> ST s ()
(*=) :: Num a => STRef s a -> STRef s a -> ST s ()
Так что ret -= i
не работает. Я попытался создать подходящий тип класса для этого:
class (Monad m) => NumMod l r m where
(+=) :: l -> r -> m ()
(-=) :: l -> r -> m ()
(*=) :: l -> r -> m ()
instance Num a => NumMod (STRef s a) (STRef s a) (ST s) where
a += b = readSTRef b >>= \b -> modifySTRef a ((+) b)
a -= b = readSTRef b >>= \b -> modifySTRef a ((+) (negate b))
a *= b = readSTRef b >>= \b -> modifySTRef a ((*) b)
instance (Num a) => NumMod (STRef s a) a (ST s) where
a += b = modifySTRef a ((+) (b))
a -= b = modifySTRef a ((+) (negate b))
a *= b = modifySTRef a ((*) (b))
Это действительно работает, но только до тех пор, пока factorial
возвращает Integer
. Как только я меняю тип возврата на что-то еще, он терпит неудачу. Я попытался создать еще один экземпляр
instance (Num a, Integral b) => NumMod (STRef s a) b (ST s) where
a += b = modifySTRef a ((+) (fromIntegral $ b))
a -= b = modifySTRef a ((+) (negate . fromIntegral $ b))
a *= b = modifySTRef a ((*) (fromIntegral b))
который выходит из строя из-за перекрывающихся экземпляров.
Можно ли создать подходящее typeclass и экземпляры для запуска factorial
для любого Integral a
? Или эта проблема всегда будет возникать?