Как преобразовать пользовательский тип в целое число в Haskell?

Я пытаюсь использовать свой собственный тип данных в haskell для простых чисел, но в настоящее время я сталкиваюсь с несколькими проблемами.

newtype Prime = Prime Integer deriving (Eq, Ord, Typeable, Show)

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

phi :: Prime -> Prime -> Integer
phi p q = (p-1)*(q-1)

phi должен возвращать целое число, потому что это больше не простое число. Все, что я получаю, это ожидаемое сообщение об ошибке:

    • Couldn't match expected type ‘Integer’ with actual type ‘Prime’
    • In the expression: (p - 1) * (q - 1)
      In an equation for ‘genPhi’: genPhi p q = (p - 1) * (q - 1)

Итак, как я могу преобразовать свой пользовательский тип в целое число? У меня нет большого опыта работы с Haskell.


comment
Я предлагал реализовать toInteger из класса типов Integral для общего преобразования, но это потребовало бы реализации всех арифметических операций над вашим типом, которые не работают для подмножества простых чисел.   -  person Bergi    schedule 31.12.2020


Ответы (2)


Вы можете развернуть Integer из конструктора данных Prime:

genPhi :: Prime -> Prime -> Integer
genPhi (Prime p) (Prime q) = (p-1) * (q-1)
person Willem Van Onsem    schedule 30.12.2020
comment
Ага, это уже. Большое спасибо за вашу быструю помощь - person Leon K.; 31.12.2020

Если у вас есть простая оболочка нового типа поверх существующего типа, хорошим приемом будет использование расширения DerivingVia:

{-# LANGUAGE DerivingVia #-}

newtype Prime = Prime { unPrime :: Integer }
   deriving Num via Integer

phi :: Prime -> Prime -> Integer
phi p q = unPrime $ (p-1)*(q-1)

Учитывая это, можно сказать:

*Main> phi (Prime 3) (Prime 5)
8

Кроме того, все остальные числовые операции будут автоматически работать с вашим типом Prime, просто используя их эквиваленты Integer.

Подробнее см. в разделе получение через. .

Примечание Как отмечено в комментариях, это не означает, что GHC любыми способами гарантирует, что результатом этих операций будет Prime; он просто позволяет вам поднимать базовые операции. (Но вы все равно могли бы совершить ту же ошибку без механизма наследования.) Если ваша программа поддерживает инвариант, согласно которому конструктор Prime всегда означает, что аргумент является простым числом, не используйте этот прием. Часто это не проблема, поскольку инвариант либо не определен четко, либо легко реализуем, а конструктор просто действует как напоминание. Но лучше четко понимать это и не использовать трюк с выводом, если вы в решающей степени зависите от него.

person alias    schedule 30.12.2020
comment
Я не уверен, что deriving Num здесь хорошая идея. Так как это означает, что теперь мы можем использовать Prime 2 * Prime 5, но Prime 10 не является простым числом. Если мы создадим интеллектуальный конструктор для Prime, то сможем предотвратить создание объектов Prime с непростыми значениями, но, сделав его экземпляром Num, потеряем эту возможность. - person Willem Van Onsem; 31.12.2020
comment
Простые числа не являются числами в смысле Num, не так ли? - person bipll; 31.12.2020
comment
Хорошая точка зрения. Я добавил уточняющую правку. Это дешевый способ подъема операций, но, возможно, не самый безопасный с точки зрения программирования, если у вас есть инварианты, которые вы хотите соблюдать. - person alias; 31.12.2020
comment
Документация для GHC.Num предполагает, что обычно предполагается, что (+) и (*) определяют кольцо. - person chepner; 31.12.2020
comment
Совершенно верно. Но я нахожу DerivingVia гораздо более описательным и понятным даже в этих более простых сценариях. - person alias; 04.01.2021