unbound-generics-0.2: src/Unbound/Generics/LocallyNameless/Name.hs
-- |
-- Module : Unbound.Generics.LocallyNameless.Name
-- Copyright : (c) 2014, Aleksey Kliger
-- License : BSD3 (See LICENSE)
-- Maintainer : Aleksey Kliger
-- Stability : experimental
--
-- Names stand for values. They may be bound or free.
{-# LANGUAGE DeriveDataTypeable
, DeriveGeneric
, ExistentialQuantification
, FlexibleContexts
, GADTs #-}
module Unbound.Generics.LocallyNameless.Name
(
-- * Names over terms
Name(..)
, isFreeName
-- * Name construction
, string2Name
, s2n
, makeName
-- * Name inspection
, name2String
, name2Integer
-- * Heterogeneous names
, AnyName(..)
) where
import Control.DeepSeq (NFData(..))
import Data.Typeable (Typeable, gcast, typeOf)
import GHC.Generics (Generic)
-- | An abstract datatype of names @Name a@ that stand for terms of
-- type @a@. The type @a@ is used as a tag to distinguish these names
-- from names that may stand for other sorts of terms.
--
-- Two names in a term are consider
-- 'Unbound.Generics.LocallyNameless.Operations.aeq' equal when they
-- are the same name (in the sense of '(==)'). In patterns, however,
-- any two names are equal if they occur in the same place within the
-- pattern. This induces alpha equivalence on terms in general.
--
-- Names may either be free or bound (see 'isFreeName'). Free names
-- may be extracted from patterns using
-- 'Unbound.Generics.LocallyNameless.Alpha.isPat'. Bound names
-- cannot be.
--
data Name a = Fn String !Integer -- free names
| Bn !Integer !Integer -- bound names / binding level + pattern index
deriving (Eq, Ord, Typeable, Generic)
instance NFData (Name a) where
rnf (Fn s n) = rnf s `seq` rnf n `seq` ()
rnf (Bn i j) = rnf i `seq` rnf j `seq` ()
-- | Returns 'True' iff the given @Name a@ is free.
isFreeName :: Name a -> Bool
isFreeName (Fn _ _) = True
isFreeName _ = False
-- | Make a free 'Name a' from a 'String'
string2Name :: String -> Name a
string2Name s = makeName s 0
-- | Synonym for 'string2Name'.
s2n :: String -> Name a
s2n = string2Name
-- | Make a name from a 'String' and an 'Integer' index
makeName :: String -> Integer -> Name a
makeName = Fn
-- | Get the integer part of a 'Name'.
name2Integer :: Name a -> Integer
name2Integer (Fn _ i) = i
name2Integer (Bn _ _) = error "Internal Error: cannot call name2Integer for bound names"
-- | Get the string part of a 'Name'.
name2String :: Name a -> String
name2String (Fn s _) = s
name2String (Bn _ _) = error "Internal Error: cannot call name2String for bound names"
instance Show (Name a) where
show (Fn "" n) = "_" ++ (show n)
show (Fn x 0) = x
show (Fn x n) = x ++ (show n)
show (Bn x y) = show x ++ "@" ++ show y
-- | An @AnyName@ is a name that stands for a term of some (existentially hidden) type.
data AnyName where
AnyName :: Typeable a => Name a -> AnyName
instance Show AnyName where
show (AnyName nm) = show nm
instance Eq AnyName where
(AnyName n1) == (AnyName n2) = case gcast n2 of
Just n2' -> n1 == n2'
Nothing -> False
instance Ord AnyName where
compare (AnyName n1) (AnyName n2) = case compare (typeOf n1) (typeOf n2) of
EQ -> case gcast n2 of
Just n2' -> compare n1 n2'
Nothing -> error "Equal type representations, but gcast failed in comparing two AnyName values"
ord -> ord