Можете ли вы создать шаблон конструктора соответствия для ограниченного параметра класса типа? - PullRequest
14 голосов
/ 22 апреля 2011

См. Пример кода ниже. Это не скомпилируется. Я думал, что, возможно, это потому, что он должен иметь один тип для первого параметра в тестовой функции. Но это не имеет смысла, потому что если я не сопоставлю шаблон с ним, чтобы он скомпилировался, я могу вызвать его как MyObj11 5 и MyObj21 5, которые являются двумя разными типами.

Так что же ограничивает, чтобы вы не могли сопоставлять шаблоны с конструкторами с ограниченным параметром класса типа? Или есть какой-то механизм, с помощью которого вы можете?

class SomeClass a where toString :: a -> String

instance SomeClass MyType1 where toString v = "MyType1"
instance SomeClass MyType2 where toString v = "MyType2"

data MyType1 = MyObj11 Int | MyObj12 Int Int 
data MyType2 = MyObj21 Int | MyObj22 Int Int 

test :: SomeClass a => a -> String
test (MyObj11 x) = "11"
test (MyObj12 x y) = "12" -- Error here if remove 3rd line: rigid type bound error
test (MyObj22 x y) = "22" -- Error here about not match MyType1.

1 Ответ

19 голосов
/ 22 апреля 2011

что ограничивает, чтобы вы не могли сопоставить шаблон с конструкторами с ограниченным параметром класса типа?

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

Вместо этого вам нужно связать различные типы поведения, которые вам нужны, с каждым экземпляром, например:

class C a where 
    toString   :: a -> String
    draw       :: a -> String

instance C MyType1 where
    toString v = "MyType1"

    draw (MyObj11 x)   = "11"  
    draw (MyObj12 x y) = "12"

instance C MyType2 where
    toString v = "MyType2"

    draw (MyObj22 x y) = "22"

data MyType1 = MyObj11 Int | MyObj12 Int Int 
data MyType2 = MyObj21 Int | MyObj22 Int Int 

test :: C a => a -> String
test x = draw x

Ветви вашей исходной функции test теперь распределены по экземплярам.

Некоторые альтернативные приемы включают использование типов данных, связанных с классом (где вы доказываете компилятору, что тип данных является общим для всех экземпляров), или просмотр шаблонов (что позволяет вам обобщить сопоставление с образцом).


Просмотр шаблонов

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

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

{-# LANGUAGE ViewPatterns #-}

class C a where 
    view       :: a -> View

data View = One Int
          | Two Int Int

data MyType1 = MyObj11 Int | MyObj12 Int Int 

instance C MyType1 where
    view (MyObj11 n) = One n
    view (MyObj12 n m) = Two n m

data MyType2 = MyObj21 Int | MyObj22 Int Int 

instance C MyType2 where
    view (MyObj21 n)   = One n
    view (MyObj22 n m) = Two n m

test :: C a => a -> String
test (view -> One n)   = "One " ++ show n
test (view -> Two n m) = "Two " ++ show n ++ show m

Обратите внимание, что синтаксис -> позволяет нам в каждом случае возвращаться к правильной функции view, просматривая пользовательский тип данных, кодирующий тип, для сопоставления с ним по шаблону.

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

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

...