Я собираюсь использовать C++ в качестве примера, чтобы показать, что мне нужно. Для сложной арифметики он имеет как комплексный, так и мнимый типы:
https://en.cppreference.com/w/c/language/arithmetic_types#Imaginary_floating_types
Эти, например. обладают тем свойством, что умножение двух чисел с мнимым типом double будет иметь тип double. Это почти то же самое, но не совсем то же самое, что и использование комплексных чисел с реальной частью, равной 0,0, но не совсем. Воображаемые типы не сохраняют явным образом, автоматически устраняют ненужные вычисления и сохраняют 0.0.
Кроме того, это предотвращает некоторые проблемы с нулями со знаком. Например. вычисление (0,0+i*a)*(0,0+i*b) приводит к (-a*b-i*0,0), если a и b отрицательны, и (-a*b+i*0,0) в противном случае. Это может удивить, если результат передается в функцию с отсечением ветвления. Воображаемый тип избегает этого нежелательного отрицания нуля.
Мой вопрос: можете ли вы определить аналогичный воображаемый тип в Haskell (в дополнение к сложному типу), а также операции (+), (-), (*) и (/) для него, чтобы они вели себя как в С++? Кажется, что, по крайней мере, с текущим определением классов Num и Fractional это невозможно, потому что (+), (-), (*) и (/) имеют сигнатуру типа a -> a -> a as, например в результате умножение двух мнимых чисел не может иметь другой тип. Однако можно ли дать другое определение для этих классов, чтобы было возможно то, что мне нужно?
Я не прошу об этом с практической целью. Я просто хочу лучше понять, на что способна система типов Haskell.
@monk Нет. Этот вопрос касается сложного типа как с реальной, так и с мнимой частью, тогда как я спрашиваю о типе, который будет иметь только мнимую часть.





Да, конечно, вы можете определить такой тип. Вы просто не сможете использовать интерфейс Num для всех его операций; но вы можете определить любые другие функции, которые вы хотите, с другими типами, возможно, даже сделав их инфиксными операторами, если это необходимо.
Вот пример типа, который отслеживает на уровне типа, является ли он воображаемым или реальным, и поддерживает сложение и умножение с альтернативным именем (вычитание и деление не требуют каких-либо новых идей, не показанных здесь):
{-# Language DataKinds #-}
{-# Language KindSignatures #-}
{-# Language TypeFamilies #-}
-- hide the Mindful data constructor
module Mindful (Mindful, real, iTimes, (+.), (*.), EqBool, KnownReality(isReal)) where
newtype Mindful (reality :: Bool) a = Mindful a deriving (Eq, Ord, Read, Show)
real :: a -> Mindful True a
real = Mindful
iTimes :: a -> Mindful False a
iTimes = Mindful
(+.) :: Num a => Mindful r a -> Mindful r a -> Mindful r a
Mindful x +. Mindful y = Mindful (x + y)
(*.) :: (KnownReality r, KnownReality r', Num a)
=> Mindful r a -> Mindful r' a -> Mindful (EqBool r r') a
xm@(Mindful x) *. ym@(Mindful y) = Mindful (iSquared * x * y) where
iSquared = if isReal xm || isReal ym then 1 else -1
type family EqBool a b where
EqBool False False = True
EqBool False True = False
EqBool True False = False
EqBool True True = True
class KnownReality r where isReal :: Mindful r a -> Bool
instance KnownReality False where isReal _ = False
instance KnownReality True where isReal _ = True
Если вам по какой-то причине нужно, чтобы имена были именно + и т. д. (я не рекомендую этого, это будет большая боль), вы можете взглянуть на еще один мой ответ об управлении пространством имен.
Трудность, с которой я столкнулся, заключается в определении (+), (-), (*) и (/) для этого типа, что, вероятно, требует переопределения Num и Fractional, если это возможно. Я немного отредактировал свой вопрос, чтобы, надеюсь, сделать его более понятным. Было бы немного неудобно использовать разные не перегружаемые функции для каждой комбинации типов.
@QuantumWiz Вы не должны имеют использовать Num для предоставления определений для +, - и т. д. только потому, что они находятся в стандартной прелюдии гордости. Num — это обычный класс с обычными методами и экземплярами. Вы можете предоставить свои собственные определения этих символов (даже свой собственный класс для определения этих символов со многими различными типами), если желаемое использование не соответствует ограничениям существующего класса. Это полностью зависит от вас, стоит ли выгода от вызова вашей операции + вместо выбора другого символа хлопот сокрытия определения Prelude.
@Ben Предоставленных определений Num (и Fractional) явно недостаточно. Проблема, с которой я сталкиваюсь, заключается в том, как сделать свои собственные определения. Я совершенно уверен, что мне придется использовать свои собственные классы, поскольку кажется, что я не могу создавать перегруженные функции без классов. Однако я не могу понять, как написать свои собственные классы (вместо Num и Fractional), чтобы иметь воображаемые и сложные типы с желаемым поведением.
@QuantumWiz Дело в том, что вы не знаете, как писать классы с нужным поведением, или в том, что вы не знаете, как использовать свои собственные классы вместо Num и Fractional?
@QuantumWiz Я добавил текст, описывающий один из способов создания такого типа без использования Num, и ссылку на текст о том, что делать, если вам действительно нравятся имена, которые предлагает Num. Я считаю, что это касается вашего обновленного вопроса.
@Ben Первый, т. Е. Я не знаю, как писать классы с нужным мне поведением. Теперь я прочитаю и попытаюсь понять обновленный ответ Даниэля Вагнера. Похоже, это может быть то, что я искал.
@DanielWagner Спасибо. Я думаю, что это, вероятно, в значительной степени близко к тому, что я хотел, что может быть достигнуто. Есть пара вопросов: 1) С таким определением (+.) кажется, что невозможно сложить действительные и мнимые числа. Впрочем, я думаю, что мог бы исправить это сам. Нужно просто заменить Bool на какой-то тип с 3 конструкторами для обозначения действительных, мнимых и комплексных чисел.
@DanielWagner 2) Кажется, что с этим определением невозможно напрямую добавить или умножить эти типы с помощью простых чисел с плавающей запятой или удвоения без предварительного помещения их в этот тип, но я думаю, что это неизбежно. Я бы предпочел, чтобы Num можно было переписать, чтобы вы могли использовать их напрямую, но я думаю, что система типов Haskell слишком жесткая (хотя во многих отношениях она очень элегантна). Я принимаю ваш ответ, потому что думаю, что лучший ответ, вероятно, невозможен.
@QuantumWiz Правильно, этот метод не позволяет складывать чисто действительные и чисто мнимые числа. Но остерегайтесь обобщать этот момент; это определение было выбрано тщательно для его представления в памяти, и добавление конструкторов резко увеличит размер представления в памяти. Я считаю, что это не система типов Haskell слишком жесткая, а именно Num слишком жесткая. Возможны альтернативные классы типов, поддерживающие смешанные аргументы (и они были испытаны до смерти; взгляните на Hackage).
@DanielWagner «добавление конструкторов увеличит размер представления в памяти», не так ли? Я думал, что это решение сделало вывод о том, что число является мнимым или реальным на уровне типа и, следовательно, предположительно во время компиляции. Если бы в этом не было необходимости, я мог бы легко реализовать нужный тип следующим образом: data MyNumber = Real Double | Imaginary Double | Complex Double Double и затем объявить его экземпляром Num. Это решит проблему нуля со знаком для действительной части чисто мнимого числа, но все равно будет выполнять действия во время выполнения, которые можно было бы сделать во время компиляции.
@DanielWagner «Я считаю, что не система типов Haskell слишком жесткая, а Num, в частности, слишком жесткая». Если вы правы, это хорошая новость, потому что (гипотетическая) переписывание Num кажется мне идеальным решением. Однако, по крайней мере, я не могу понять, как переписать Num, чтобы он делал то, что я хочу здесь.
@QuantumWiz Правильно, это решение делает вывод на уровне типа и, следовательно, во время компиляции. Но вы предлагаете изменить это решение, и предлагаемое вами изменение будет стоить вам денег. Один из способов переписать Num похож на class Add a b where type Sum a b; (+) :: a -> b -> Sum a b и аналогично для умножения.
Отвечает ли это на ваш вопрос? Как создать общий сложный тип в haskell?