Как работает равенство для числовых типов?

Я вижу, что Haskell позволяет сравнивать различные числовые типы:

*Main> :t 3
3 :: Num t => t
*Main> :t 3.0
3.0 :: Fractional t => t
*Main> 3 == 3.0
True

Где исходный код экземпляра Eq для числовых типов? Если я создам новый тип, такой как ComplexNumber, могу ли я расширить == для его работы? (Я мог бы захотеть, чтобы комплексные числа без мнимых частей были потенциально равны действительным числам.)


person Ellen Spertus    schedule 01.08.2016    source источник
comment
Это всего лишь пример (расширенных?) правил по умолчанию в GHCi.   -  person crockeea    schedule 01.08.2016
comment
Здесь может возникнуть путаница: хотя 3 и 3.0 имеют разные полиморфные типы, в выражении 3 == 3.0 им обоим присваивается один и тот же мономорфный тип перед вычислением равенства. Тип (==) говорит, что он должен принимать два аргумента одного типа: Eq a => a -> a -> Bool. Я подозреваю, что неправильное понимание этой тонкости и привело к вопросу.   -  person Daniel Wagner    schedule 01.08.2016
comment
позволяет сравнивать различные числовые типы. Попробуйте это: length "abc" == 3.0. Это работает?   -  person n. 1.8e9-where's-my-share m.    schedule 01.08.2016
comment
@н.м. Это работает в GHCI.   -  person Ellen Spertus    schedule 01.08.2016
comment
Какая версия GHCi?   -  person n. 1.8e9-where's-my-share m.    schedule 02.08.2016


Ответы (2)


«Haskell позволяет сравнивать различные числовые типы», нет, это не так. На самом деле Haskell позволяет определять разные типы одними и теми же литералами. В частности, вы можете сделать

Prelude> let a = 3.7 :: Double
Prelude> let b = 1   :: Double
Prelude> a + b
4.7

OTOH, если бы я объявил их явно с конфликтующими типами, добавление не удалось бы:

Prelude> let a = 3.7 :: Double
Prelude> let b = 1   :: Int
Prelude> a + b

<interactive>:31:5:
    Couldn't match expected type ‘Double’ with actual type ‘Int’
    In the second argument of ‘(+)’, namely ‘b’
    In the expression: a + b

Теперь Double не самый общий возможный тип ни для a, ни для b. На самом деле все числовые литералы полиморфны, но прежде чем произойдет какая-либо операция (например, сравнение на равенство), такой полиморфный тип должен быть привязан к конкретному мономорфному типу. Нравится,

Prelude> (3.0 :: Double) == (3 :: Double)
True

Поскольку ==, вопреки вашей предпосылке, на самом деле требует, чтобы обе стороны имели одинаковый тип, вы можете опустить подпись с любой стороны, ничего не меняя:

Prelude> 3.0 == (3 :: Double)
True

На самом деле, даже без какой-либо аннотации типа, GHCi по-прежнему будет обрабатывать обе стороны как Double. Это связано с типом по умолчанию — в данном конкретном случае Fractional является самым сильным ограничением для общего типа номера, а для Fractional тип по умолчанию — Double. OTOH, если бы обе стороны были целочисленными литералами, то GHCi выбрал бы Integer. Иногда это может иметь значение, например

Prelude> 10000000000000000 == 10000000000000001
False

но

Prelude> 10000000000000000 ==(10000000000000001 :: Double)
True

потому что в последнем случае последняя 1 теряется при ошибке с плавающей запятой.

person leftaroundabout    schedule 01.08.2016
comment
Спасибо. Значит, я не смогу проверить равенство элементам нового типа, который я создал? - person Ellen Spertus; 01.08.2016
comment
Не из ранее существовавших типов, нет. И даже не к другим типам, которые вы также создали. Вы можете сравнивать только тип a с типом a. - person Louis Wasserman; 02.08.2016
comment
@espertus: вы можете легко сравнить два значения, которые оба относятся к новому типу, созданному вами самостоятельно. Просто напишите экземпляр Eq для тот тип. - person leftaroundabout; 02.08.2016

Ничего волшебного здесь не происходит. 3 может быть либо вещественным числом, либо целым числом, но его тип унифицируется с действительным числом по сравнению с 3.0.

Обратите внимание на класс типа:

class Eq a where
  eq :: a -> a -> Bool

Так что eq действительно сравнивает только вещи одного типа. И ваш пример 3 == 3.0 получает унифицированные типы и становится 3.0 == 3.0 внутри.

Я не уверен в каких-либо трюках с типом, чтобы сделать его унифицированным с определенным пользователем типом комплексного числа. Моя интуиция подсказывает мне, что это невозможно.

person clinux    schedule 01.08.2016
comment
Неа. Это может быть сделано. Проверьте это: haskell.org/onlinereport/haskell2010/ Теперь, если бы этот механизм дефолта был более общим, я был бы счастливее... - person Alec; 02.08.2016