Сопоставление с образцом по типу полиморфного параметра - альтернативы
Допустим, мне нужны разные выходные данные в зависимости от типа полиморфного параметра функции. Моя первоначальная попытка не удалась с каким-то загадочным сообщением об ошибке:
choice :: a -> Int
choice (_ :: Int) = 0
choice (_ :: String) = 1
choice _ = 2
Однако мы можем легко это исправить, обернув нужные типы в разные конструкторы данных и используя их при сопоставлении с образцом:
data Choice a = IntChoice Int | StringChoice String | OtherChoice a
choice :: Choice a -> Int
choice (IntChoice _) = 0
choice (StringChoice _) = 1
choice (OtherChoice _) = 2
Вопрос: знаете ли вы способ обойти это? Есть ли функция в Haskell2010, GHC или любом из расширений, которая позволяет мне использовать первый вариант (или что-то подобное)?
2 ответа
Вопрос: знаете ли вы способ обойти это? Есть ли функция в Haskell2010, GHC или любом из расширений, которая позволяет мне использовать первый вариант (или что-то подобное)?
Нет, ни в Haskell 2010, ни в каком-либо расширении GHC нет функции, позволяющей написать функцию типа
choice :: a -> Int
чье возвращаемое значение зависит от типа его аргумента. Вы можете рассчитывать на такую функцию, никогда не существующую в будущем.
Даже имея хаки для проверки внутреннего представления данных GHC во время выполнения, невозможно различить значение типа Int
от значения, тип которого является новым типом Int
: эти типы имеют идентичные представления времени выполнения.
Int
Ваша функция возвращает значение, которое существует во время выполнения, поэтому оно должно быть определено другим значением, которое существует во время выполнения. Это может быть
обычное значение, как в вашем
data Choice a = IntChoice Int | StringChoice String | OtherChoice a; choice :: Choice a -> Int
пример илисловарь классов типов, использующий либо собственный класс, как в ответе Дэвида Янга, либо встроенный
Typeable
учебный класс:choice :: Typeable a => a -> Int choice a | typeOf a == typeOf (undefined :: Int) = 0 | typeOf a == typeOf (undefined :: String) = 1 | otherwise = 2
Это смешивает два разных вида полиморфизма. То, что вы хотите, это специальный полиморфизм, который осуществляется с помощью классов типов. Вид полиморфизма функции типа a -> Int
это параметрический полиморфизм. С параметрическим полиморфизмом, одно определение функции для choice
должен работать для любого возможного типа a
, В этом случае это означает, что он не может использовать значение типа a
так как он ничего не знает об этом, так choice
должен быть постоянной функцией, такой как choice _ = 3
, На самом деле это дает вам очень сильные гарантии того, что может сделать функция, просто посмотрев на ее тип (это свойство называется параметричностью).
С помощью класса типов вы можете реализовать свой пример следующим образом:
class ChoiceClass a where
choice :: a -> Int
instance ChoiceClass Int where
choice _ = 0
instance ChoiceClass String where
choice _ = 1
instance ChoiceClass a where
choice _ = 2
Теперь я должен отметить, что этот подход класса типов часто неправильный, особенно когда кто-то, кто только начинает, хочет использовать его. Вы определенно не хотите делать это, чтобы избежать простого типа, как Choice
введите свой вопрос. Это может добавить много сложности, и разрешение экземпляра может сначала сбить с толку. Обратите внимание: чтобы заставить работать решение класса типов, необходимо включить два расширения: FlexibleInstances
а также TypeSynonymInstances
поскольку String
это синоним [Char]
, OverlappingInstances
также необходимо, потому что классы типов работают в предположении "открытого мира" (это означает, что каждый может позже прийти и добавить экземпляр для нового типа, и это необходимо учитывать). Это не обязательно плохо, но здесь это признак ползучей сложности, вызванной использованием решения класса типов по сравнению с гораздо более простым решением типа данных. OverlappingInstances
в частности, может сделать вещи сложнее думать и работать.