Можете ли вы перегружать + в Haskell?
Хотя я видел все виды странных вещей в примере кода на Haskell - я никогда не видел оператора плюс перегрузка. Есть ли в этом что-то особенное?
Допустим, у меня есть такой тип, как Pair, и я хочу иметь что-то вроде
Pair(2,4) + Pair(1,2) = Pair(3,6)
Можно ли сделать это в Haskell?
Мне просто любопытно, потому что я знаю, что в Scala это возможно довольно элегантно.
5 ответов
да
(+)
является частью Num
класс типов, и все, кажется, чувствуют, что вы не можете определить (*)
и т. д. для вашего типа, но я категорически не согласен.
newtype Pair a b = Pair (a,b) deriving (Eq,Show)
Я думаю Pair a b
было бы лучше, или мы могли бы просто использовать тип (a,b)
напрямую, но...
Это очень похоже на декартово произведение двух моноидов, групп, колец или чего-либо другого в математике, и есть стандартный способ определения числовой структуры на нем, который было бы разумно использовать.
instance (Num a,Num b) => Num (Pair a b) where
Pair (a,b) + Pair (c,d) = Pair (a+c,b+d)
Pair (a,b) * Pair (c,d) = Pair (a*c,b*d)
Pair (a,b) - Pair (c,d) = Pair (a-c,b-d)
abs (Pair (a,b)) = Pair (abs a, abs b)
signum (Pair (a,b)) = Pair (signum a, signum b)
fromInteger i = Pair (fromInteger i, fromInteger i)
Теперь мы перегружены (+)
очевидным образом, но также ушла вся свинья и перегружена (*)
и все остальные Num
работает так же, очевидно, знакомо, как математика делает это для пары. Я просто не вижу проблемы с этим. На самом деле я думаю, что это хорошая практика.
*Main> Pair (3,4.0) + Pair (7, 10.5)
Pair (10,14.5)
*Main> Pair (3,4.0) + 1 -- *
Pair (4,5.0)
*
- Заметить, что fromInteger
применяется к числовым литералам, таким как 1
так что это было истолковано в этом контексте как Pair (1,1.0) :: Pair Integer Double
, Это также довольно приятно и удобно.
Я постараюсь ответить на этот вопрос очень прямо, так как вы стремитесь получить прямое "да или нет" при перегрузке (+). Ответ - да, вы можете его перегрузить. Есть два способа перегрузить его напрямую, без каких-либо других изменений, и один способ "правильно" перегрузить его, который требует создания экземпляра Num для вашего типа данных. Правильный путь разработан в других ответах, поэтому я не буду его обсуждать.
Изменить: Обратите внимание, что я не рекомендую способ, описанный ниже, просто документирую его. Вы должны реализовать класс типов Num, а не то, что я здесь пишу.
Первый (и самый "неправильный") способ перегрузки (+) - просто скрыть функцию Prelude.+ И определить собственную функцию с именем (+), которая работает с вашим типом данных.
import Prelude hiding ((+)) -- hide the autoimport of +
import qualified Prelude as P -- allow us to refer to Prelude functions with a P prefix
data Pair a = Pair (a,a)
(+) :: Num a => Pair a -> Pair a -> Pair a -- redefinition of (+)
(Pair (a,b)) + (Pair (c,d)) = Pair ((P.+) a c,(P.+) b d ) -- using qualified (+) from Prelude
Здесь вы можете увидеть, что нам нужно пройти через некоторые искажения, чтобы скрыть обычное определение (+) от импорта, но нам все еще нужен способ обратиться к нему, поскольку это единственный способ сделать быстрое добавление машины (это примитив операция).
Второй (чуть менее неправильный) способ сделать это - определить свой собственный класс типов, который включает только новый оператор, который вы называете (+). Вам все равно придется скрывать старое (+), чтобы Haskell не запутался.
import Prelude hiding ((+))
import qualified Prelude as P
data Pair a = Pair (a,a)
class Addable a where
(+) :: a -> a -> a
instance Num a => Addable (Pair a) where
(Pair (a,b)) + (Pair (c,d)) = Pair ((P.+) a c,(P.+) b d )
Это немного лучше, чем первый вариант, потому что он позволяет вам использовать ваш новый (+) для множества различных типов данных в вашем коде.
Но ни один из них не рекомендуется, потому что, как вы можете видеть, очень неудобно обращаться к обычному оператору (+), который определен в классе типов Num. Хотя haskell позволяет вам переопределить (+), все Prelude и библиотеки ожидают оригинального (+) определения. К счастью для вас, (+) определен в классе типов, так что вы можете просто сделать Pair экземпляром Num. Это, вероятно, лучший вариант, и это то, что рекомендовали другие ответчики.
Проблема, с которой вы сталкиваетесь, заключается в том, что, возможно, в классе типов Num определено слишком много функций (+ - одна из них). Это просто историческая случайность, и теперь использование Num настолько распространено, что было бы трудно изменить его сейчас. Вместо того, чтобы разбивать эти функции на отдельные классы типов для каждой функции (чтобы их можно было переопределить отдельно), все они объединяются. В идеале Prelude должен иметь класс типов Addable, класс типов Subtractable и т. Д., Которые позволяют вам определять экземпляр для одного оператора за раз без необходимости реализовывать все, что есть в Num.
Как бы то ни было, дело в том, что вы будете сражаться в тяжелом положении, если захотите написать новый (+) только для вашего типа данных Pair. Слишком большая часть другого кода на Haskell зависит от класса типов Num и его текущего определения.
Вы можете заглянуть в Числовую Прелюдию, если вы ищете переиздание Прелюдии в голубом небе, которое пытается избежать некоторых ошибок текущей. Вы заметите, что они заново реализовали Prelude просто как библиотеку, взломать компилятор не нужно, хотя это огромная задача.
Перегрузка в Haskell доступна только с использованием классов типов. В этом случае, (+)
принадлежит к Num
тип класса, так что вы должны предоставить Num
экземпляр для вашего типа.
Тем не мение, Num
также содержит другие функции, и экземпляр с хорошим поведением должен реализовывать их все согласованным образом, что в общем случае не имеет смысла, если ваш тип не представляет какое-то число.
Поэтому, если это не так, я бы рекомендовал вместо этого определить новый оператор. Например,
data Pair a b = Pair a b
deriving Show
infixl 6 |+| -- optional; set same precedence and associativity as +
Pair a b |+| Pair c d = Pair (a+c) (b+d)
Затем вы можете использовать его как любой другой оператор:
> Pair 2 4 |+| Pair 1 2
Pair 3 6
Перегрузка в Haskell стала возможной благодаря классам типов. Для хорошего обзора вы можете посмотреть этот раздел в Learn You a Haskell.
(+)
оператор является частью Num
Тип класса из Prelude:
class (Eq a, Show a) => Num a where
(+), (*), (-) :: a -> a -> a
negate :: a -> a
...
Так что если вы хотите определение +
чтобы работать для пар, вы должны предоставить экземпляр.
Если у вас есть тип:
data Pair a = Pair (a, a) deriving (Show, Eq)
Тогда у вас может быть такое определение:
instance Num a => Num (Pair a) where
Pair (x, y) + Pair (u, v) = Pair (x+u, y+v)
...
Пробивая это в ghci
дает нам:
*Main> Pair (1, 2) + Pair (3, 4)
Pair (4,6)
Однако, если вы собираетесь дать экземпляр для +
Вы также должны предоставлять экземпляр для всех других функций в этом классе типов, что не всегда имеет смысл.
Если вы только хотите (+)
оператор, а не все Num
операторы, вероятно, у вас есть Monoid
например, например Monoid
Экземпляр пары выглядит так:
class (Monoid a, Monoid b) => Monoid (a, b) where
mempty = (mempty, mempty)
(a1, b1) `mappend` (a2, b2) = (a1 `mappend` a2, b1 `mappend` b2)
Ты можешь сделать (++)
псевдоним mappend
тогда вы можете написать код так:
(1,2) ++ (3,4) == (4,6)
("hel", "wor") ++ ("lo", "ld") == ("hello", "world")