Господа гусары, занимаюсь приседаниями с системой типов Haskell. Решил соорудить эдакую систему кастования типов. Что имею в виду: есть тип, есть его субтип, построенный на базе родителя, субтип ведет себя так-же, как родитель и может быть безопасно преобразован в родителя, но обратно не каждый родитель кастуется к указанному субтипу.
Вот код
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, FlexibleContexts, UndecidableInstances, OverlappingInstances, ScopedTypeVariables #-}
class SubtypeOf a b | a -> b where
upcast :: a -> b
downcastSafe :: b -> Maybe a
downcast :: b -> a
downcast b = case downcastSafe b of
Nothing -> error $ "can not downcast the value"
Just a -> a
data Range i a = Range {unRange :: a}
deriving Show
class Rangeable i where
lowLim :: (Ord a, Num a) => t i a -> a
highLim :: (Ord a, Num a) => t i a -> a
instance (Rangeable i, Num a, Ord a) => SubtypeOf (Range i a) a where
upcast = unRange
downcastSafe b | (b >= lowLim p) && (b <= highLim p) = Just p
| otherwise = Nothing
where
p :: Range i a
p = Range b
data R1 = R1
instance Rangeable R1 where
lowLim _ = 0
highLim _ = 10
data R2 = R2
instance Rangeable R2 where
lowLim _ = -10
highLim _ = 100
data Multiple i a = Multiple {unMultiple :: a}
deriving Show
class Multipable i where
mValue :: (Num a) => t i a -> a
data M2 = M2
instance Multipable M2 where
mValue _ = 2
instance (Multipable i, Integral a) => SubtypeOf (Multiple i a) a where
upcast = unMultiple
downcastSafe b | b `mod` (mValue p) == 0 = Just p
| otherwise = Nothing
where
p :: (Multiple i a)
p = Multiple b
instance (Multipable i, RealFrac a) => SubtypeOf (Multiple i a) a where
upcast = unMultiple
downcastSafe b | x == 0 = Just p
| otherwise = Nothing
where
x = d - (fromIntegral $ floor d)
d = b / (mValue p)
p :: Multiple i a
p = Multiple b
SubtypeOf связывает два типа, a - субтип, b - родитель (возможно я не совсем правильно употребляю здесь слово субтип). Range - тип, хранящий число в определенном интервале, тип имеет фантомный тип. R1 и R2 - фантомные типы для указания диапазона в котором находится число. Инстанс SubtypeOf для (Range i a) a написан в обобщенном виде, так, что можно кастовать к Range с любым фантомным типом инстанцирующим Rangeable, и все работает.
Теперь, захотел я сделать тип «число кратное N» с фантомынм типом, указывающим это самое N. Тоже самое - Multiple - наш тип «число кратное N», M2 - фантомный тип, говорящий, что число должно быть кратно 2, дальше проблемы.
Кратность числа для целых и дробных чисел проверяется по-разному: для целых есть операция вычисления остатка от деления, если она == 0, то число кратное делителю. Для дробных нужно поделить, а потом проверить, равна ли дробная часть результата нулю. И нам нужно два инстанса для SubtypeOf с целым типом-родителем и с дробным.
instance (Multipable i, Integral a) => SubtypeOf (Multiple i a) a where
instance (Multipable i, RealFrac a) => SubtypeOf (Multiple i a) a where
Однако, компилятор считает, что эти инстансы дублирующиеся
/home/razor/casting.hs:51:10:
Duplicate instance declarations:
instance [overlap ok] (Multipable i, Integral a) =>
SubtypeOf (Multiple i a) a
-- Defined at /home/razor/casting.hs:51:10
instance [overlap ok] (Multipable i, RealFrac a) =>
SubtypeOf (Multiple i a) a
-- Defined at /home/razor/casting.hs:59:10
Failed, modules loaded: none.
Как так ? Float инстанцирует RealFrac но не инстанцирует Integral, Integer - все наоборот. По логике, мы могли бы использовать такие инстансы, и ошибка должна была бы возникать только если a инстанцирует сразу и то и другое, так, что компилятор не может угадать, какой инстанс использовать. Но таких типов даже нету, все типы либо Integral, либо RealFrac. Поясните, почему так нельзя, и как можно ?
Кастую KblCb quasimoto adzeitor Miguel dmitry_malikov qnikst