{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleContexts #-} -- | This module provides the 'NominalSupport' type class. It consists -- of those types for which the support can be calculated. With the -- exception of function types, most 'Nominal' types are also -- in 'NominalSupport'. -- -- We also provide some generic programming so that instances of -- 'NominalSupport' can be automatically derived in most cases. -- -- This module exposes implementation details of the Nominal library, -- and should not normally be imported. Users of the library should -- only import the top-level module "Nominal". module Nominal.NominalSupport where import Data.Map (Map) import qualified Data.Map as Map import Data.Set (Set) import qualified Data.Set as Set import GHC.Generics import Nominal.ConcreteNames import Nominal.Atom import Nominal.Nominal -- ---------------------------------------------------------------------- -- * Literal strings -- | A wrapper around strings. This is used to denote any literal -- strings whose values should not clash with the names of bound -- variables. For example, if a term contains a constant symbol /c/, -- the name /c/ should not also be used as the name of a bound -- variable. This can be achieved by marking the string with -- 'Literal', like this -- -- > data Term = Var Atom | Const (Literal String) | ... -- -- Another way to use 'Literal' is in the definition of custom -- 'NominalSupport' instances. See -- <#CUSTOM "Defining custom instances"> for an example. newtype Literal = Literal String deriving (Show) instance Nominal Literal where (•) = basic_action -- ---------------------------------------------------------------------- -- * Support -- | Something to be avoided can be an atom or a string. data Avoidee = A Atom | S String deriving (Eq, Ord, Show) -- | This type provides an internal representation for the support of -- a nominal term, i.e., the set of atoms (and constants) occurring in -- it. This is an abstract type with no exposed structure. The only way -- to construct a value of type 'Support' is to use the function -- 'support'. newtype Support = Support (Set Avoidee) -- | The empty support. support_empty :: Support support_empty = Support Set.empty -- | The union of a list of supports. support_unions :: [Support] -> Support support_unions xs = Support (Set.unions [ x | Support x <- xs ]) -- | The union of two supports. support_union :: Support -> Support -> Support support_union (Support x) (Support y) = Support (Set.union x y) -- | Add an atom to the support. support_insert :: Atom -> Support -> Support support_insert a (Support x) = Support (Set.insert (A a) x) -- | A singleton support. support_atom :: Atom -> Support support_atom a = Support (Set.singleton (A a)) -- | Delete an atom from the support. support_delete :: Atom -> Support -> Support support_delete a (Support s) = Support (Set.delete (A a) s) -- | Delete a list of atoms from the support. support_deletes :: [Atom] -> Support -> Support support_deletes [] s = s support_deletes (a:as) s = support_deletes as (support_delete a s) -- | Add a literal string to the support. support_string :: String -> Support support_string s = Support (Set.singleton (S s)) -- | Convert the support to a list of strings. strings_of_support :: Support -> Set String strings_of_support (Support s) = Set.map name s where name (A a) = show a name (S s) = s -- ---------------------------------------------------------------------- -- * The NominalSupport class -- | 'NominalSupport' is a subclass of 'Nominal' consisting of those -- types for which the support can be calculated. With the notable -- exception of function types, most 'Nominal' types are also -- instances of 'NominalSupport'. -- -- In most cases, instances of 'NominalSupport' can be automatically -- derived. See <#DERIVING "Deriving generic instances"> for -- information on how to do so, and -- <#CUSTOM "Defining custom instances"> for how to write custom -- instances. class (Nominal t) => NominalSupport t where -- | Compute a set of atoms and strings that should not be used as -- the names of bound variables. support :: t -> Support default support :: (Generic t, GNominalSupport (Rep t)) => t -> Support support x = gsupport (from x) -- ---------------------------------------------------------------------- -- * Open for printing -- | A variant of 'open' which moreover chooses a name for the bound -- atom that does not clash with any free name in its scope. This -- function is mostly useful for building custom pretty-printers for -- nominal terms. Except in pretty-printers, it is equivalent to -- 'open'. -- -- Usage: -- -- > open_for_printing sup t (\x s sup' -> body) -- -- Here, /sup/ = 'support' /t/. For printing to be efficient (roughly -- O(/n/)), the support must be pre-computed in a bottom-up fashion, -- and then passed into each subterm in a top-down fashion (rather -- than re-computing it at each level, which would be O(/n/²)). For -- this reason, 'open_for_printing' takes the support of /t/ as an -- additional argument, and provides /sup'/, the support of /s/, as an -- additional parameter to the body. -- -- The correct use of this function is subject to -- <#CONDITION Pitts's freshness condition>. atom_open_for_printing :: (Nominal t) => Support -> BindAtom t -> (Atom -> t -> Support -> s) -> s atom_open_for_printing sup t@(BindAtom ng f) k = with_fresh_atom_named n ng (\a -> k a (force (f a)) (sup' a)) where n = rename_fresh (strings_of_support sup) ng sup' a = support_insert a sup -- ---------------------------------------------------------------------- -- * NominalSupport instances -- $ Most of the time, instances of 'NominalSupport' should be derived using -- @deriving (Generic, NominalSupport)@, as in this example: -- -- > {-# LANGUAGE DeriveGeneric #-} -- > {-# LANGUAGE DeriveAnyClass #-} -- > -- > data Term = Var Atom | App Term Term | Abs (Bind Atom Term) -- > deriving (Generic, NominalSupport) -- -- In the case of non-nominal types (typically base types such as -- 'Double'), a 'NominalSupport' instance can be defined using -- 'basic_support': -- -- > instance NominalSupport MyType where -- > support = basic_support -- | A helper function for defining 'NominalSupport' instances -- for non-nominal types. basic_support :: t -> Support basic_support t = support () -- Base cases instance NominalSupport Atom where support = support_atom instance NominalSupport Bool where support = basic_support instance NominalSupport Integer where support = basic_support instance NominalSupport Int where support = basic_support instance NominalSupport Char where support = basic_support instance NominalSupport Double where support = basic_support instance NominalSupport Float where support = basic_support instance NominalSupport Ordering where support = basic_support instance NominalSupport (Basic t) where support = basic_support instance NominalSupport Literal where support (Literal s) = support_string s -- Generic instances instance (NominalSupport t) => NominalSupport [t] instance NominalSupport () instance (NominalSupport t, NominalSupport s) => NominalSupport (t,s) instance (NominalSupport t, NominalSupport s, NominalSupport r) => NominalSupport (t,s,r) instance (NominalSupport t, NominalSupport s, NominalSupport r, NominalSupport q) => NominalSupport (t,s,r,q) instance (NominalSupport t, NominalSupport s, NominalSupport r, NominalSupport q, NominalSupport p) => NominalSupport (t,s,r,q,p) instance (NominalSupport t, NominalSupport s, NominalSupport r, NominalSupport q, NominalSupport p, NominalSupport o) => NominalSupport (t,s,r,q,p,o) instance (NominalSupport t, NominalSupport s, NominalSupport r, NominalSupport q, NominalSupport p, NominalSupport o, NominalSupport n) => NominalSupport (t,s,r,q,p,o,n) instance (NominalSupport a) => NominalSupport (Maybe a) instance (NominalSupport a, NominalSupport b) => NominalSupport (Either a b) -- Special instances instance (NominalSupport t) => NominalSupport (BindAtom t) where support (BindAtom ng f) = with_fresh_atom ng (\a -> support_delete a (support (f a))) instance (NominalSupport t) => NominalSupport (Defer t) where support t = support (force t) instance (Ord k, NominalSupport k, NominalSupport v) => NominalSupport (Map k v) where support map = support (Map.toList map) instance (Ord k, NominalSupport k) => NominalSupport (Set k) where support set = support (Set.toList set) -- ---------------------------------------------------------------------- -- * Generic programming for NominalSupport -- | A version of the 'NominalSupport' class suitable for generic programming. class GNominalSupport f where gsupport :: f a -> Support instance GNominalSupport V1 where gsupport x = undefined -- Does not occur, because V1 is an empty type. instance GNominalSupport U1 where gsupport U1 = support_empty instance (GNominalSupport a, GNominalSupport b) => GNominalSupport (a :*: b) where gsupport (a :*: b) = support_union (gsupport a) (gsupport b) instance (GNominalSupport a, GNominalSupport b) => GNominalSupport (a :+: b) where gsupport (L1 x) = gsupport x gsupport (R1 x) = gsupport x instance (GNominalSupport a) => GNominalSupport (M1 i c a) where gsupport (M1 x) = gsupport x instance (NominalSupport a) => GNominalSupport (K1 i a) where gsupport (K1 x) = support x