Ваша проблема на самом деле не такая, как в этом вопросе. В вопросе, на который вы ссылались, у Дерека Терна была функция, которая, как он знал, принимает Set a
, но не может сопоставить шаблон. В вашем случае вы пишете функцию, которая будет принимать любой a
, у которого есть экземпляр Show
; вы не можете сказать, какой тип вы просматриваете во время выполнения, и можете полагаться только на функции, доступные для любого типа Show
able. Если вы хотите, чтобы функция выполняла разные действия для разных типов данных, это называется специальным полиморфизмом и поддерживается в 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
able, является GraphvizLabel
, но компилятору трудно это сказать. Например, если вы воспользуетесь этим кодом и напишете toGVLabel [1,2,3]
в приглашении GHCi, вы получите сообщение об ошибке, поскольку 1
имеет тип Num a => a
, а Char
может быть экземпляром Num
! Вы должны явно указать toGVLabel ([1,2,3] :: [Int])
, чтобы заставить его работать. Опять же, это, вероятно, излишне тяжелая техника для решения вашей проблемы. Вместо этого, если вы можете ограничить то, что, по вашему мнению, будет преобразовано в метки, что весьма вероятно, вы можете просто указать эти вещи! Но если вы действительно хотите, чтобы Show
ability подразумевал GraphvizLabel
ability, вот что вам нужно:
{-# 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
всегда следует подходить с осторожностью.
person
Antal Spector-Zabusky
schedule
15.10.2010