Можете ли вы структурировать конструкторы соответствия по типу ограничения класса?

См. пример кода ниже. Он не будет компилироваться. Я подумал, что, возможно, это потому, что для первого параметра в тестовой функции он должен иметь один тип. Но это не имеет смысла, потому что если я не сопоставляю ему шаблон, поэтому он будет компилироваться, я могу назвать его как с 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

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

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

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

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 в каждом экземпляре, просматривая кодировку пользовательского типа данных для каждого типа, чтобы сопоставить соответствие ему.

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

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