{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeSynonymInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Diagrams.Core.Names -- Copyright : (c) 2011-2015 diagrams-core team (see LICENSE) -- License : BSD-style (see LICENSE) -- Maintainer : diagrams-discuss@googlegroups.com -- -- This module defines a type of names which can be used for referring -- to subdiagrams, and related types. -- ----------------------------------------------------------------------------- module Diagrams.Core.Names (-- * Names -- ** Atomic names AName(..) , _AName -- ** Names , Name(..), IsName(..), (.>) -- ** Qualifiable , Qualifiable(..) ) where import Control.Lens hiding ((.>)) import qualified Data.Map as M import Data.Semigroup import qualified Data.Set as S import Data.Typeable import Diagrams.Core.Transform import Diagrams.Core.Measure ------------------------------------------------------------ -- Names ------------------------------------------------- ------------------------------------------------------------ -- | Class for those types which can be used as names. They must -- support 'Typeable' (to facilitate extracting them from -- existential wrappers), 'Ord' (for comparison and efficient -- storage) and 'Show'. -- -- To make an instance of 'IsName', you need not define any methods, -- just declare it. -- -- WARNING: it is not recommended to use -- @GeneralizedNewtypeDeriving@ in conjunction with @IsName@, since -- in that case the underlying type and the @newtype@ will be -- considered equivalent when comparing names. For example: -- -- @ -- newtype WordN = WordN Int deriving (Show, Ord, Eq, Typeable, IsName) -- @ -- -- is unlikely to work as intended, since @(1 :: Int)@ and @(WordN 1)@ -- will be considered equal as names. Instead, use -- -- @ -- newtype WordN = WordN Int deriving (Show, Ord, Eq, Typeable, IsName) -- instance IsName WordN -- @ class (Typeable a, Ord a, Show a) => IsName a where toName :: a -> Name toName = Name . (:[]) . AName instance IsName () instance IsName Bool instance IsName Char instance IsName Int instance IsName Float instance IsName Double instance IsName Integer instance IsName a => IsName [a] instance IsName a => IsName (Maybe a) instance (IsName a, IsName b) => IsName (a,b) instance (IsName a, IsName b, IsName c) => IsName (a,b,c) -- | Atomic names. @AName@ is just an existential wrapper around -- things which are 'Typeable', 'Ord' and 'Show'. data AName where AName :: (Typeable a, Ord a, Show a) => a -> AName deriving Typeable instance IsName AName where toName = Name . (:[]) instance Eq AName where (AName a1) == (AName a2) = case cast a2 of Nothing -> False Just a2' -> a1 == a2' instance Ord AName where (AName a1) `compare` (AName a2) = case cast a2 of Just a2' -> a1 `compare` a2' Nothing -> typeOf a1 `compare` typeOf a2 instance Show AName where showsPrec d (AName a) = showParen (d > 10) $ showString "AName " . showsPrec 11 a -- | Prism onto 'AName'. _AName :: (Typeable a, Ord a, Show a) => Prism' AName a _AName = prism' AName (\(AName a) -> cast a) -- | A (qualified) name is a (possibly empty) sequence of atomic names. newtype Name = Name [AName] deriving (Eq, Ord, Semigroup, Monoid, Typeable) instance Rewrapped Name Name instance Wrapped Name where type Unwrapped Name = [AName] _Wrapped' = iso (\(Name ns) -> ns) Name instance Show Name where showsPrec d (Name xs) = case xs of [] -> showParen (d > 10) $ showString "Name []" [n] -> showParen (d > 10) $ showString "toName " . showsName 11 n (n:ns) -> showParen (d > 5) $ showsName 6 n . go ns where go (y:ys) = showString " .> " . showsName 6 y . go ys go _ = id where showsName dd (AName a) = showsPrec dd a instance IsName Name where toName = id -- | Convenient operator for writing qualified names with atomic -- components of different types. Instead of writing @toName a1 \<\> -- toName a2 \<\> toName a3@ you can just write @a1 .> a2 .> a3@. (.>) :: (IsName a1, IsName a2) => a1 -> a2 -> Name a1 .> a2 = toName a1 <> toName a2 -- | Instances of 'Qualifiable' are things which can be qualified by -- prefixing them with a name. class Qualifiable q where -- | Qualify with the given name. (.>>) :: IsName a => a -> q -> q -- | Of course, names can be qualified using @(.>)@. instance Qualifiable Name where (.>>) = (.>) instance Qualifiable a => Qualifiable (TransInv a) where (.>>) n = over (_Unwrapping' TransInv) (n .>>) instance (Qualifiable a, Qualifiable b) => Qualifiable (a,b) where n .>> (a,b) = (n .>> a, n .>> b) instance (Qualifiable a, Qualifiable b, Qualifiable c) => Qualifiable (a,b,c) where n .>> (a,b,c) = (n .>> a, n .>> b, n .>> c) instance Qualifiable a => Qualifiable [a] where n .>> as = map (n .>>) as instance (Ord a, Qualifiable a) => Qualifiable (S.Set a) where n .>> s = S.map (n .>>) s instance Qualifiable a => Qualifiable (M.Map k a) where n .>> m = fmap (n .>>) m instance Qualifiable a => Qualifiable (b -> a) where n .>> f = (n .>>) . f instance Qualifiable a => Qualifiable (Measured n a) where n .>> m = fmap (n .>>) m infixr 5 .>> infixr 5 .>