Сопоставление шаблонов по «ИЛИ» двух новых типов в haskell

Я использую библиотеку диаграмм решений в программе Haskell. Для этого я хочу объявить 2 разных (новых) типа, которые отслеживают, с какой диаграммой решений я имею дело. Библиотека, которую я использую, — это cudd, а базовым типом диаграммы решений является DdNode, но мой вопрос связан только с haskell.

newtype Bdd = ToBdd Cudd.Cudd.DdNode
newtype Zdd = ToZdd Cudd.Cudd.DdNode

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

data Dd = ToBdd Bdd | ToZdd Zdd

printDdInfo :: Dd -> IO()
printDdInfo (ToZdd dd) = do
    putStrLn "Hello, zdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo (ToBdd dd) = do
    putStrLn "Hello, bdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd

printDdInfo :: Either Bdd Zdd -> IO()
printDdInfo (ToZdd dd) = do
    putStrLn "Hello, zdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo (ToBdd dd) = do
    putStrLn "Hello, bdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd
    
printDdInfo :: Either Bdd Zdd -> IO()
printDdInfo dd = case dd of
  Zdd dd -> do
    putStrLn "Hello, bdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd
  Bdd dd -> do
    putStrLn "Hello, bdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd

Все эти методы не помогли. Каков самый элегантный способ написания этого кода? Спасибо за внимание.


person Daniel Miedema    schedule 15.07.2020    source источник
comment
Какой тип Cudd.Cudd.cuddPrintDdInfo? Требуется ли Cudd.Cudd.DdNode?   -  person Aplet123    schedule 15.07.2020
comment
Я предпочитаю передавать DdNode напрямую, а не пытаться сделать это с помощью магии типов. Если это слишком грязно, я думаю, что @DanielWagner заниженное предложение вполне неплохо. printDdInfo :: (HasNode a) => a -> IO ()   -  person luqui    schedule 17.07.2020


Ответы (2)


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

newtype Dd x = ToDd (Cudd.Cudd.DdNode)
data B
data Z

Теперь вы можете различать Dd B и Dd Z, когда хотите, и работать полиморфно с Dd x, когда вам все равно.

В современном GHC Haskell, если вы хотите указать, что B и Z являются единственными тегами, вы можете использовать расширения DataKinds и KindSignatures и сделать это следующим образом:

newtype Dd (x :: DdTag) = ToDd (Cudd.Cudd.DdNode)
data DdTag = B | Z

В этом контексте вы будете иметь дело с Dd 'B и Dd 'Z, где одинарная кавычка (произносится как тик) продвигает конструктор данных на уровень типа.

Чтобы написать функцию, которая ведет себя по-разному в зависимости от того, какой тег имеет тип, вам понадобится класс.

class Zoop tag where
  zoop :: Dd tag -> Int
  zeep :: Char -> Dd tag
  zaaaaap :: Dd tag -> Dd tag

Теперь вы можете написать экземпляр Zoop для B (или 'B) и один для Z (или 'Z), чтобы пользователи могли использовать метод для любого из них. Помните, однако, что типы стираются при компиляции, поэтому вам понадобится ограничение Zoop a каждый раз, когда вы захотите применить эти методы с полиморфными тегами.

person dfeuer    schedule 15.07.2020
comment
Мне это нравится, но вы все равно должны ответить, как достигается собственно «совпадение с образцом», о чем и был вопрос. (Класс, я бы сказал.) - person leftaroundabout; 16.07.2020
comment
@leftaroundabout, хорошая мысль. Я постараюсь не забыть отредактировать это в . - person dfeuer; 16.07.2020
comment
Эй, это именно то, что я ищу! Какие преимущества я получу, указав, что это единственные теги? - person Daniel Miedema; 16.07.2020
comment
@DanielMiedema, в основном ясность намерений. Это также может помочь людям, пишущим экземпляры класса (или семейства типов), относящиеся к вашему типу, знать, что они рассмотрели все варианты. - person dfeuer; 16.07.2020
comment
@leftaroundabout, как тебе это? - person dfeuer; 16.07.2020

Я бы пошел с чем-то очень похожим на ваш первый подход, но вам нужно переименовать конструкторы, отличные от конструкторов нового типа:

data Dd = FromBdd Bdd | FromZdd Zdd

printDdInfo :: Dd -> IO()
printDdInfo (FromZdd (ToZdd dd)) = do
    putStrLn "Hello, zdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo (FromBdd (ToBdd dd)) = do
    putStrLn "Hello, bdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd

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

data DdKind = IsBdd | IsZdd

data Dd = Dd { ddKind :: DdKind
             , ddImplementation :: Cudd.Cudd.DdNode }

printDdInfo :: Dd -> IO()
printDdInfo (Dd ddk dd) = do
    putStrLn $ case ddk of
       IsBdd -> "Hello, bdd!"
       IsZdd -> "Hello, zdd!"
    Cudd.Cudd.cuddPrintDdInfo manager dd
person leftaroundabout    schedule 15.07.2020
comment
Спасибо, рассмотрю ваши варианты. Какова терминология части { }? Таким образом, я могу посмотреть его особенности в Интернете. - person Daniel Miedema; 15.07.2020
comment
Это синтаксис записи в Haskell. - person leftaroundabout; 15.07.2020
comment
Третьей альтернативой может быть использование класса типов (возможно, class HasNode a where implementation :: a -> Cudd.Cudd.DdNode), который имеет экземпляры для Bdd и Zdd, а затем полностью пропустить определения данных. - person Daniel Wagner; 15.07.2020