2015-05-19 4 views
1

Предположим, мне нужен другой выход в зависимости от типа полиморфного параметра функции. Моя первая попытка терпит неудачу с некоторым загадочным сообщением об ошибке:Соответствие шаблону по типу полиморфного параметра - альтернативы

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 или любом расширении, которое позволяет мне использовать первый вариант (или что-то подобное)?

+1

'Data.Typeable' позволяет запрашивать типы во время выполнения, но это не следует использовать слегка. – chi

ответ

4

Вопрос: Вы знаете способ обхода этого? Есть ли функция в Haskell2010, GHC или любом расширении, которое позволяет мне использовать первый вариант (или что-то подобное)?

Нет, нет функции либо в Haskell 2010 или предоставленный любое расширение GHC, что позволяет написать функцию типа

choice :: a -> Int 

которого возвращаемого значение зависит от типа аргумента. Вы можете рассчитывать на такую ​​функцию, которая никогда не существует в будущем.

Даже с взломами инспектировать внутреннее представление данных GHC во время выполнения, это невозможно отличить значение типа Int от значения, тип которого является Newtype из Int: эти типы имеют одинаковые представления во время выполнения.

Int, возвращаемый вашей функцией, является значением, которое существует во время выполнения, поэтому его необходимо определить другим значением, которое существует во время выполнения. Это может быть либо

  1. обычное значение, как в вашем data Choice a = IntChoice Int | StringChoice String | OtherChoice a; choice :: Choice a -> Int например, или

  2. класс типа словаря, используя либо пользовательский класс, как в ответ Дэвида Янга, или встроенный в Typeable классе :

    choice :: Typeable a => a -> Int 
    choice a 
        | typeOf a == typeOf (undefined :: Int) = 0 
        | typeOf a == typeOf (undefined :: String) = 1 
        | otherwise        = 2 
    
+0

Благодарим вас за разъяснение альтернатив. _ "невозможно отличить значение типа Int от значения, тип которого является новым типом Int: эти типы имеют идентичные представления времени выполнения. _ _ - это не то, что означает' TypeSynonymInstances'? (упоминается в [ответе Дэвида] (http://stackoverflow.com/a/30333292/3041008)). Будете ли вы так добры и предоставляете определение функции 'choice', используя класс типа Typeable? – mucaho

+3

@mucaho 'TypeSynonymInstances' специально для экземпляров класса типов. В частности, он позволяет создавать экземпляр для синонима типа. Синоним типа - это тип, который обрабатывается системой типа типа аналогично другому типу (в этом случае 'type String = [Char]'). Даже с этим расширением и 'OverlappingInstances' вы не можете сделать экземпляр как' String', так и '[Char]', хотя, поскольку они являются синонимами. 'newtype's ', с другой стороны, * * обрабатываются типами типа различными типами, но имеют точное представление времени исполнения. –

5

Это путает два разных типа полиморфизма. То, что вы хотите, это ad-hoc полиморфизм, который осуществляется через классы типов. Типом полиморфизма функции типа 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, в частности, может усложнить работу и работать.

+1

Вам также нужно «OverlappingInstances» для фактического использования этого класса. Я считаю, что это расширение особенно сложно и трудно понять. Например. http://stackoverflow.com/questions/29504107/which-dictionary-does-ghc-choose-when-more-than-one-is-in-scope – chi

+0

@chi Спасибо, что довели это! На мгновение я подумал, что это не обязательно, потому что «выбор» a'' работал без него (хотя я и думал, что это будет). Однако использовать другие два экземпляра невозможно. Я нахожу, что расширение трудно понять, и я определенно считаю, что это добавляет дополнительную сложность, которую следует избегать, если это возможно. Я обновил свой ответ, чтобы упомянуть об этом. –

+0

Спасибо за ценную информацию. Поэтому вы предлагаете придерживаться конструкторов данных, если это возможно, правильно? – mucaho