2017-02-06 7 views
4

Я определяю свой собственный тип данных с числовыми числами как упражнение для обучения, и я столкнулся с проблемой перегрузки abs вместе с другими членами Num. Насколько я знаю, только одно определение экземпляра допускается на класс типов, но если бы я мог, я бы сделал что-то вроде этого:Различные ограничения типа для одного и того же экземпляра

instance Num a => Num (Complex a) where 
    (+) (Complex ra ia) (Complex rb ib) = Complex (ra + rb) (ia + ib) 
    (-) (Complex ra ia) (Complex rb ib) = Complex (ra - rb) (ia - ib) 
    (*) (Complex ra ia) (Complex rb ib) = Complex (ra*rb - ia*ib) (ra*ib + rb*ia) 
    fromInteger r = Complex (fromInteger r) 0 

instance Floating a => Num (Complex a) where 
    abs (Complex r i) = Complex (sqrt $ r^2 + i^2) 0 

или

instance Floating a => Floating (Complex a) where 
    abs (Complex r i) = Complex (sqrt $ r^2 + i^2) 0 

Потому что ни один из отличных abs членов требуют Floating типов, и я не хочу ограничивать их только Floating типами, но функция abs очень важна, и я не хочу излишне исключать ее.
Есть ли способ, которым я могу иметь функции (+), (-) и (*) работать со всеми числовыми типами, сохраняя при этом abs?

Согласно 7.6.3.4. Overlapping instances в руководстве системы GHC, несколько экземпляров могут перекрываться, если они отличаются от типа ограничения (?) Вне контекста (например, instance C [a] и instance C [Int]), с компилятором выбирая наиболее конкретный экземпляр для данного случая , но в нем ничего не говорится о различии только контекста (например, instance C [a] и instance Integral a => C [a]).

+2

К сожалению, иерархия типов для «чисел» (или «базовая алгебра» или что-то, что ее следует назвать) была довольно плохо продумана с самого начала. Может быть, вам лучше обойти это полностью для вашего «комплекса а»? Существует [альтернативная иерархия] (https://hackage.haskell.org/package/numeric-prelude), но это может быть немного переполнено для вашей ситуации. Сказанное по-разному: вам, похоже, нужен принципиальный способ создания классного ряда для сложных чисел. Стандартные типы типов, такие как 'Num', с которыми вы хотите работать, очень ad-hoc :-( – gspr

+2

@gspr: иерархия чисел может быть не оптимальной, но на практике она работает очень хорошо. Что касается этого варианта использования - почти во всех реальных приложениях комплексных чисел вам понадобится «Floating» в любом случае, сложные числа в основном полезны в контексте корней и/или экспонент (для описания колебаний). – leftaroundabout

+1

@leftaroundabout: Согласен, хотя можно было бы представить практический и полезный экземпляр для «Ratio Integer». – gspr

ответ

4

Основным источником боли является то, что иерархия чисел Прелюдии была определена как не слишком сложная - для большинства вещей она работает отлично. Это один из тех крайних случаев, когда на самом деле нет (хотя, как указывает @leftaroundabout, я не уверен, что существует множество приложений для Complex над чем-то неFloating).

Ваши варианты в

  • Добавить Floating a ограничение на Num (Complex a). Это то, что чувствует себя наиболее естественным для меня и имеет наибольший смысл с точки зрения типа класса - shoehorning в instance Num a => Num (Complex a) разбивает абстракцию Num, потому что у нее нет понятия abs.
  • Используйте цифровую иерархию мельчайших зерен. На ум приходит numeric-prelude. В том, что вы найдете следующее (разложить на несколько модулей):

    class (Field.C a) => Algebraic.C a where 
        sqrt :: a -> a 
    
    class (Ring.C a) => Field.C a where 
        (/)   :: a -> a -> a 
        recip   :: a -> a 
        fromRational' :: Rational -> a 
        (^-)   :: a -> Integer -> a 
    
    class (Ring.C a) => Absolute.C a where 
        abs :: a -> a 
        signum :: a -> a 
    
    class (Additive.C a) => Ring.C a where 
        (*)   :: a -> a -> a 
        one   :: a 
        fromInteger :: Integer -> a 
        (^)   :: a -> Integer -> a 
    
    class Additive.C a where 
        zero  :: a 
        (+), (-) :: a -> a -> a 
        negate :: a -> a 
    

    В вашем случае, вы сделали бы случаи instance Additive.C a => Additive.C (Complex a), instance Ring.C a => Ring.C (Complex a) и instance Algebraic.C a => Absolute.C (Complex a).

  • Если мне еще не удалось убедить вас отказаться от этого безумия, не стесняйтесь посмотреть this page on advanced overlap. Помимо сложного и тяжелого сценария (и нужно включить тонну языковых расширений), это решение не является довольно общим (вам все равно придется вручную выбирать, какие типы идут в какой экземпляр).

Смежные вопросы