Я хочу создать комплексный тип для представления комплексных чисел.

Следующие работы:

Prelude> data Complex = Complex Int Int

Prelude> :t Complex
Complex :: Int -> Int -> Complex

Как я могу изменить это, чтобы принять любой тип Num, а не просто Int.

Я попробовал следующее:

Prelude> data Complex a = Num a => Complex a a

Но получил это:

* Data constructor `Complex' has existential type variables, a context, or a specialised result type
    Complex :: forall a. Num a => a -> a -> Complex a
    (Use ExistentialQuantification or GADTs to allow this)
* In the definition of data constructor `Complex'
  In the data type declaration for `Complex'

Я не совсем уверен, что делать с этой ошибкой. Любая помощь приветствуется.

8
ksceriath 19 Окт 2017 в 12:25

3 ответа

Лучший ответ

Традиционный data в Haskell - это просто data . Ему не нужно ничего знать о свойствах его полей, ему просто нужно уметь хранить их. Следовательно, нет реальной необходимости ограничивать поля в этой точке; просто сделай это

data Complex a = Complex !a !a

(! потому что строгие поля лучше для производительности).

Конечно, когда вы реализуете экземпляр Num, вам понадобится ограничение:

instance (Num a) => Num (Complex a) where
  fromInteger = (`Complex`0) . fromInteger
  Complex r i + Complex ρ ι = Complex (r+ρ) (i+ι)
  ...

... на самом деле вам нужно гораздо более сильное ограничение RealFloat a для реализации abs, по крайней мере, так стандартная версия делает это. (Это означает, что Complex Int на самом деле не может быть использован, не со стандартной иерархией Num; вам нужен, например, Complex Double.)

Тем не менее, также возможно вставить ограничение в сам тип данных. Синтаксис ExistentialTypes, который вы пробовали, сильно ограничивает и не подходит для этого; вместо этого вам нужен GADT

data Complex a where
   Complex :: Num a => a -> a -> Complex a

Имея это на месте, вы могли бы реализовать, например, дополнение без упоминания каких-либо ограничений в подписи

cplxAdd :: Complex a -> Complex a -> Complex a
cplxAdd (Complex r i) (Complex ρ ι) = Complex (r+ρ) (i+ι)

Теперь вам нужно будет выполнить Num всякий раз, когда вы пытаетесь создать значение Complex. Это означает, что вам все равно понадобится явное ограничение в экземпляре Num.

Кроме того, эта версия потенциально намного медленнее, потому что словарь Num действительно должен храниться в представлении времени выполнения.

11
leftaroundabout 19 Окт 2017 в 09:57

Конструкторы типов не могут быть ограничены в чистом Haskell, только функции могут. Так что предполагается, что вы объявляете

data Complex a = Complex a a

А затем ограничить функции, такие как

conjugate :: (Num a) => Complex a -> Complex a
conjugate (Complex x y) = Complex x (-y)

Фактически, тип и ограничение для conjugate могут быть получены компилятором, поэтому вы можете просто определить реализацию:

conjugate (Complex x y) = Complex x (-y)

Однако, если вы действительно хотите ограничить конструктор типа Complex, вы можете включить некоторые расширения , которые его включают, а именно ExistentialQuantification или GADTs, в качестве компилятора предлагает. Для этого добавьте эту строку в самое начало вашего файла:

{-# LANGUAGE ExistentialQuantification #-}

Или

{-# LANGUAGE GADTs #-}

Они называются прагмы.

3
lisyarus 19 Окт 2017 в 09:54

Хотя вы можете, как указано в сообщении компилятора, использовать ExistentialQuantification, вы также можете определить тип следующим образом:

data Complex a = Complex a a deriving (Show, Eq)

Это абсолютно неограниченный тип, поэтому, возможно, более подходящим будет другое имя ... Этот тип часто называют Pair ...

Однако, когда вы пишете функции, вы можете ограничить значения, содержащиеся в типе:

myFunction :: Num a => Complex a -> a
myFunction (Complex x y) = x + y
2
Mark Seemann 19 Окт 2017 в 09:37