Ваша проблема на самом деле не такая, как в этом вопросе.В вопросе, на который вы ссылались, у Дерека Турна была функция, которую он знал, что взял Set a
, но не мог сопоставить образец.В вашем случае вы пишете функцию, которая будет принимать любой a
с экземпляром Show
;вы не можете сказать, какой тип вы просматриваете во время выполнения, и можете полагаться только на функции, доступные для любого Show
способного типа.Если вы хотите, чтобы функция делала разные вещи для разных типов данных, это называется специальный полиморфизм и поддерживается в Haskell с классами типов, такими как Show
.(Это в отличие от параметрического полиморфизма , когда вы пишете функцию, подобную head (x:_) = x
, которая имеет тип head :: [a] -> a
; универсальный без ограничений a
- это то, что делает этот параметрический вместо этого.)что вы хотите, вы должны будете создать свой собственный класс типов и создать его экземпляр, когда вам это нужно.Однако, это немного сложнее, чем обычно, потому что вы хотите сделать все, что является частью Show
, неявно частью вашего нового класса типов.Это требует некоторых потенциально опасных и, вероятно, излишне мощных расширений GHC.Вместо этого, почему бы не упростить вещи?Вероятно, вы можете определить подмножество типов, которые вам действительно нужно распечатать таким образом.Сделав это, вы можете написать код следующим образом:
{-# LANGUAGE TypeSynonymInstances #-}
module GraphvizTypeclass where
import qualified Data.Map as M
import Data.Map (Map)
import Data.List (intercalate) -- For output formatting
surround :: String -> String -> String -> String
surround before after = (before ++) . (++ after)
squareBrackets :: String -> String
squareBrackets = surround "[" "]"
quoted :: String -> String
quoted = let replace '"' = "\\\""
replace c = [c]
in surround "\"" "\"" . concatMap replace
class GraphvizLabel a where
toGVItem :: a -> String
toGVLabel :: a -> String
toGVLabel = squareBrackets . ("label=" ++) . toGVItem
-- We only need to print Strings, Ints, Chars, and Maps.
instance GraphvizLabel String where
toGVItem = quoted
instance GraphvizLabel Int where
toGVItem = quoted . show
instance GraphvizLabel Char where
toGVItem = toGVItem . (: []) -- Custom behavior: no single quotes.
instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where
toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :)
in intercalate "," . M.foldWithKey kvfn []
toGVLabel = squareBrackets . toGVItem
В этой настройке все, что мы можем вывести в Graphviz, является экземпляром GraphvizLabel
;функция toGVItem
заключает в кавычки, а toGVLabel
помещает все это в квадратные скобки для немедленного использования.(Я мог бы испортить часть нужного вам форматирования, но эта часть - только пример.) Затем вы объявляете, что является экземпляром GraphvizLabel
, и как превратить его в элемент.Флаг TypeSynonymInstances
позволяет нам писать instance GraphvizLabel String
вместо instance GraphvizLabel [Char]
;это безвредно.
Теперь, если вам действительно нужно все с экземпляром Show
, который также должен быть экземпляром GraphvizLabel
, есть способ.Если вам это не нужно, не используйте этот код!Если вам нужно сделать это, вы должны взять на вооружение языковые расширения UndecidableInstances
и OverlappingInstances
(и менее страшно названные FlexibleInstances
).Причина этого заключается в том, что вы должны утверждать, что все , которое Show
способно, является GraphvizLabel
, но компилятору это трудно сказать.Например, если вы используете этот код и напишите toGVLabel [1,2,3]
в приглашении GHCi, вы получите ошибку, так как 1
имеет тип Num a => a
, а Char
может быть экземпляром Num
!Вы должны явно указать toGVLabel ([1,2,3] :: [Int])
, чтобы заставить его работать.Опять же, это, вероятно, неоправданно тяжелая техника для решения вашей проблемы.Вместо этого, если вы можете ограничить то, что, по вашему мнению, будет преобразовано в метки, что весьма вероятно, вы можете просто указать эти вещи вместо этого!Но если вы действительно хотите Show
способность подразумевать GraphvizLabel
способность, это то, что вам нужно:
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances
, UndecidableInstances, OverlappingInstances #-}
-- Leave the module declaration, imports, formatting code, and class declaration
-- the same.
instance GraphvizLabel String where
toGVItem = quoted
instance Show a => GraphvizLabel a where
toGVItem = quoted . show
instance (GraphvizLabel k, GraphvizLabel v) => GraphvizLabel (Map k v) where
toGVItem = let kvfn k v = ((toGVItem k ++ "=" ++ toGVItem v) :)
in intercalate "," . M.foldWithKey kvfn []
toGVLabel = squareBrackets . toGVItem
Обратите внимание, что ваши конкретные дела (GraphvizLabel String
и GraphvizLabel (Map k v)
) оставайся таким же;Вы только что свернули дела Int
и Char
в дело GraphvizLabel a
.Помните, UndecidableInstances
означает именно то, что он говорит: компилятор не может сказать , проверяются ли экземпляры, или вместо этого сделает цикл проверки типов!В этом случае я вполне уверен, что все здесь на самом деле решаемо (но если кто-то заметит, где я ошибаюсь, , пожалуйста, , дайте мне знать).Тем не менее, к использованию UndecidableInstances
следует всегда подходить с осторожностью.