To observe the error:
$ ghc -c -O Alpha.hs
$ ghc -c -O -v Calc.hs
# note Calc.hs hangs on "Called arity analysis"To make the compilation take longer, add more branches to the Expr datatype.
| -- | | |
| -- Module : Unbound.Generics.LocallyNameless.Alpha | |
| -- Copyright : (c) 2014, Aleksey Kliger | |
| -- License : BSD3 (See LICENSE) | |
| -- Maintainer : Aleksey Kliger | |
| -- Stability : experimental | |
| -- | |
| -- Use the 'Alpha' typeclass to mark types that may contain 'Name's. | |
| {-# LANGUAGE DefaultSignatures | |
| , FlexibleContexts | |
| , GADTs | |
| , DeriveGeneric | |
| , TypeOperators #-} | |
| module Alpha ( | |
| -- * Name-aware opertions | |
| Alpha(..) | |
| -- * Names | |
| , Name | |
| , AnyName | |
| -- * Binding | |
| , Bind | |
| -- * Embedding terms in patterns | |
| , Embed(..) | |
| -- * Binder variables | |
| , DisjointSet(..) | |
| , inconsistentDisjointSet | |
| , singletonDisjointSet | |
| , isConsistentDisjointSet | |
| -- * Implementation details | |
| , NthPatFind | |
| , NamePatFind | |
| , AlphaCtx | |
| , initialCtx | |
| , patternCtx | |
| , termCtx | |
| , isTermCtx | |
| , incrLevelCtx | |
| , decrLevelCtx | |
| , isZeroLevelCtx | |
| -- * Internal | |
| , gaeq | |
| , gisPat | |
| , gisTerm | |
| , gnthPatFind | |
| , gnamePatFind | |
| , gswaps | |
| , gfreshen | |
| ) where | |
| import Control.Applicative (Applicative(..), (<$>)) | |
| import Control.Arrow (first) | |
| import Control.Monad (liftM) | |
| import Data.Function (on) | |
| import Data.Foldable (Foldable(..)) | |
| import Data.List (intersect) | |
| import Data.Monoid (Monoid(..), (<>)) | |
| import Data.Ratio (Ratio) | |
| import Data.Typeable (Typeable, gcast, typeOf) | |
| import GHC.Generics | |
| -- | The @Fresh@ type class governs monads which can generate new | |
| -- globally unique 'Name's based on a given 'Name'. | |
| class Monad m => Fresh m where | |
| -- | Generate a new globally unique name based on the given one. | |
| fresh :: Name a -> m (Name a) | |
| -- | Types that are instances of @Alpha@ may participate in name representation. | |
| -- | |
| -- Minimal instance is entirely empty, provided that your type is an instance of | |
| -- 'Generic'. | |
| class (Show a) => Alpha a where | |
| -- | See 'Unbound.Generics.LocallyNameless.Operations.aeq'. | |
| aeq' :: AlphaCtx -> a -> a -> Bool | |
| default aeq' :: (Generic a, GAlpha (Rep a)) => AlphaCtx -> a -> a -> Bool | |
| aeq' c = (gaeq c) `on` from | |
| -- | @isPat x@ dynamically checks whether @x@ can be used as a valid pattern. | |
| isPat :: a -> DisjointSet AnyName | |
| default isPat :: (Generic a, GAlpha (Rep a)) => a -> DisjointSet AnyName | |
| isPat = gisPat . from | |
| -- | @isPat x@ dynamically checks whether @x@ can be used as a valid term. | |
| isTerm :: a -> Bool | |
| default isTerm :: (Generic a, GAlpha (Rep a)) => a -> Bool | |
| isTerm = gisTerm . from | |
| -- | @isEmbed@ is needed internally for the implementation of | |
| -- 'isPat'. @isEmbed@ is true for terms wrapped in 'Embed' and zero | |
| -- or more occurrences of 'Shift'. The default implementation | |
| -- simply returns @False@. | |
| isEmbed :: a -> Bool | |
| isEmbed _ = False | |
| -- | If @a@ is a pattern, finds the @n@th name in the pattern | |
| -- (starting from zero), returning the number of names encountered | |
| -- if not found. | |
| nthPatFind :: a -> NthPatFind | |
| default nthPatFind :: (Generic a, GAlpha (Rep a)) => a -> NthPatFind | |
| nthPatFind = gnthPatFind . from | |
| -- | If @a@ is a pattern, find the index of the given name in the pattern. | |
| namePatFind :: a -> NamePatFind | |
| default namePatFind :: (Generic a, GAlpha (Rep a)) => a -> NamePatFind | |
| namePatFind = gnamePatFind . from | |
| -- | See 'Unbound.Generics.LocallyNameless.Operations.swaps'. Apply | |
| -- the given permutation of variable names to the given pattern. | |
| swaps' :: AlphaCtx -> Perm AnyName -> a -> a | |
| default swaps' :: (Generic a, GAlpha (Rep a)) => AlphaCtx -> Perm AnyName -> a -> a | |
| swaps' ctx perm = to . gswaps ctx perm . from | |
| -- | See 'Unbound.Generics.LocallyNameless.Operations.freshen'. Rename the free variables | |
| -- in the given term to be distinct from all other names seen in the monad @m@. | |
| freshen' :: Fresh m => AlphaCtx -> a -> m (a, Perm AnyName) | |
| default freshen' :: (Generic a, GAlpha (Rep a), Fresh m) => AlphaCtx -> a -> m (a, Perm AnyName) | |
| freshen' ctx = liftM (first to) . gfreshen ctx . from | |
| -- | The result of @'nthPatFind' a i@ is @Left k@ where @k@ is the | |
| -- number of names in pattern @a@ with @k < i@ or @Right x@ where @x@ | |
| -- is the @i@th name in @a@ | |
| type NthPatFind = Integer -> Either Integer AnyName | |
| -- | The result of @'namePatFind' a x@ is either @Left i@ if @a@ is a pattern that | |
| -- contains @i@ free names none of which are @x@, or @Right j@ if @x@ is the @j@th name | |
| -- in @a@ | |
| type NamePatFind = AnyName -> Either Integer Integer -- Left - names skipped over | |
| -- Right - index of the name we found | |
| -- | The "Generic" representation version of 'Alpha' | |
| class GAlpha f where | |
| gaeq :: AlphaCtx -> f a -> f a -> Bool | |
| gisPat :: f a -> DisjointSet AnyName | |
| gisTerm :: f a -> Bool | |
| gnthPatFind :: f a -> NthPatFind | |
| gnamePatFind :: f a -> NamePatFind | |
| gswaps :: AlphaCtx -> Perm AnyName -> f a -> f a | |
| gfreshen :: Fresh m => AlphaCtx -> f a -> m (f a, Perm AnyName) | |
| instance (Alpha c) => GAlpha (K1 i c) where | |
| gaeq ctx (K1 c1) (K1 c2) = aeq' ctx c1 c2 | |
| gisPat = isPat . unK1 | |
| gisTerm = isTerm . unK1 | |
| gnthPatFind = nthPatFind . unK1 | |
| gnamePatFind = namePatFind . unK1 | |
| gswaps ctx perm = K1 . swaps' ctx perm . unK1 | |
| gfreshen ctx = liftM (first K1) . freshen' ctx . unK1 | |
| instance GAlpha f => GAlpha (M1 i c f) where | |
| gaeq ctx (M1 f1) (M1 f2) = gaeq ctx f1 f2 | |
| gisPat = gisPat . unM1 | |
| gisTerm = gisTerm . unM1 | |
| gnthPatFind = gnthPatFind . unM1 | |
| gnamePatFind = gnamePatFind . unM1 | |
| gswaps ctx perm = M1 . gswaps ctx perm . unM1 | |
| gfreshen ctx = liftM (first M1) . gfreshen ctx . unM1 | |
| instance GAlpha U1 where | |
| gaeq _ctx _ _ = True | |
| gisPat _ = mempty | |
| gisTerm _ = True | |
| gnthPatFind _ = Left | |
| gnamePatFind _ _ = Left 0 | |
| gswaps _ctx _perm _ = U1 | |
| gfreshen _ctx _ = return (U1, mempty) | |
| instance GAlpha V1 where | |
| gaeq _ctx _ _ = False | |
| gisPat _ = mempty | |
| gisTerm _ = False | |
| gnthPatFind _ = Left | |
| gnamePatFind _ _ = Left 0 | |
| gswaps _ctx _perm _ = undefined | |
| gfreshen _ctx _ = return (undefined, mempty) | |
| instance (GAlpha f, GAlpha g) => GAlpha (f :*: g) where | |
| gaeq ctx (f1 :*: g1) (f2 :*: g2) = | |
| gaeq ctx f1 f2 && gaeq ctx g1 g2 | |
| gisPat (f :*: g) = gisPat f <> gisPat g | |
| gisTerm (f :*: g) = gisTerm f && gisTerm g | |
| gnthPatFind (f :*: g) i = case gnthPatFind f i of | |
| Left i' -> gnthPatFind g i' | |
| Right ans -> Right ans | |
| gnamePatFind (f :*: g) n = case gnamePatFind f n of | |
| Left j -> case gnamePatFind g n of | |
| Left i -> Left $! j + i | |
| Right k -> Right $! j + k | |
| Right k -> Right k | |
| gswaps ctx perm (f :*: g) = | |
| gswaps ctx perm f :*: gswaps ctx perm g | |
| gfreshen ctx (f :*: g) = do | |
| (g', perm2) <- gfreshen ctx g | |
| (f', perm1) <- gfreshen ctx (gswaps ctx perm2 f) | |
| return (f' :*: g', perm1 <> perm2) | |
| instance (GAlpha f, GAlpha g) => GAlpha (f :+: g) where | |
| gaeq ctx (L1 f1) (L1 f2) = gaeq ctx f1 f2 | |
| gaeq ctx (R1 g1) (R1 g2) = gaeq ctx g1 g2 | |
| gaeq _ctx _ _ = False | |
| gisPat (L1 f) = gisPat f | |
| gisPat (R1 g) = gisPat g | |
| gisTerm (L1 f) = gisTerm f | |
| gisTerm (R1 g) = gisTerm g | |
| gnthPatFind (L1 f) i = gnthPatFind f i | |
| gnthPatFind (R1 g) i = gnthPatFind g i | |
| gnamePatFind (L1 f) n = gnamePatFind f n | |
| gnamePatFind (R1 g) n = gnamePatFind g n | |
| gswaps ctx perm (L1 f) = L1 (gswaps ctx perm f) | |
| gswaps ctx perm (R1 f) = R1 (gswaps ctx perm f) | |
| gfreshen ctx (L1 f) = liftM (first L1) (gfreshen ctx f) | |
| gfreshen ctx (R1 f) = liftM (first R1) (gfreshen ctx f) | |
| -- ============================================================ | |
| -- Alpha instances for the usual types | |
| instance Alpha Int where | |
| aeq' _ctx i j = i == j | |
| isPat _ = mempty | |
| isTerm _ = True | |
| nthPatFind _ = Left | |
| namePatFind _ _ = Left 0 | |
| swaps' _ctx _p i = i | |
| freshen' _ctx i = return (i, mempty) | |
| instance Alpha Char where | |
| aeq' _ctx i j = i == j | |
| isPat _ = mempty | |
| isTerm _ = True | |
| nthPatFind _ = Left | |
| namePatFind _ _ = Left 0 | |
| swaps' _ctx _p i = i | |
| freshen' _ctx i = return (i, mempty) | |
| instance Alpha Integer where | |
| aeq' _ctx i j = i == j | |
| isPat _ = mempty | |
| isTerm _ = True | |
| nthPatFind _ = Left | |
| namePatFind _ _ = Left 0 | |
| swaps' _ctx _p i = i | |
| freshen' _ctx i = return (i, mempty) | |
| instance Alpha Bool | |
| instance Alpha a => Alpha (Maybe a) | |
| instance Alpha a => Alpha [a] | |
| instance Alpha () | |
| instance (Alpha a,Alpha b) => Alpha (Either a b) | |
| instance (Alpha a,Alpha b) => Alpha (a,b) | |
| instance (Alpha a,Alpha b,Alpha c) => Alpha (a,b,c) | |
| instance (Alpha a, Alpha b,Alpha c, Alpha d) => Alpha (a,b,c,d) | |
| instance (Alpha a, Alpha b,Alpha c, Alpha d, Alpha e) => | |
| Alpha (a,b,c,d,e) | |
| -- ============================================================ | |
| -- Alpha instances for interesting types | |
| instance Typeable a => Alpha (Name a) where | |
| aeq' ctx n1 n2 = | |
| if isTermCtx ctx | |
| then n1 == n2 -- in terms, better be the same name | |
| else True -- in a pattern, names are always equivlent (since | |
| -- they're both bound, so they can vary). | |
| isPat n = if isFreeName n | |
| then singletonDisjointSet (AnyName n) | |
| else inconsistentDisjointSet | |
| isTerm _ = True | |
| nthPatFind nm i = | |
| if i == 0 then Right (AnyName nm) else Left $! i-1 | |
| namePatFind nm1 (AnyName nm2) = | |
| case gcast nm1 of | |
| Just nm1' -> if nm1' == nm2 then Right 0 else Left 1 | |
| Nothing -> Left 1 | |
| swaps' ctx perm nm = | |
| if isTermCtx ctx | |
| then case applyPerm perm (AnyName nm) of | |
| AnyName nm' -> | |
| case gcast nm' of | |
| Just nm'' -> nm'' | |
| Nothing -> error "Internal error swaps' on a Name returned permuted name of wrong sort" | |
| else nm | |
| freshen' ctx nm = | |
| if not (isTermCtx ctx) | |
| then do | |
| nm' <- fresh nm | |
| return (nm', single (AnyName nm) (AnyName nm')) | |
| else error "freshen' on a Name in term position" | |
| instance Alpha AnyName where | |
| aeq' ctx x y = | |
| if x == y | |
| then True | |
| else | |
| -- in a term unequal variables are unequal, in a pattern it's | |
| -- ok. | |
| not (isTermCtx ctx) | |
| isTerm _ = True | |
| isPat n@(AnyName nm) = if isFreeName nm | |
| then singletonDisjointSet n | |
| else inconsistentDisjointSet | |
| swaps' ctx perm n = | |
| if isTermCtx ctx | |
| then applyPerm perm n | |
| else n | |
| freshen' ctx (AnyName nm) = | |
| if isTermCtx ctx | |
| then error "LocallyNameless.freshen' on AnyName in Term mode" | |
| else do | |
| nm' <- fresh nm | |
| return (AnyName nm', single (AnyName nm) (AnyName nm')) | |
| nthPatFind nm i = | |
| if i == 0 then Right nm else Left $! i - 1 | |
| namePatFind nmHave nmWant = | |
| if nmHave == nmWant | |
| then Right 0 | |
| else Left 1 | |
| data Name a = Fn String !Integer -- free names | |
| | Bn !Integer !Integer -- bound names / binding level + pattern index | |
| deriving (Eq, Ord, Typeable, Generic) | |
| -- | 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 | |
| -- | A term of type @'Bind' p t@ is a term that binds the free | |
| -- variable occurrences of the variables in pattern @p@ in the term | |
| -- @t@. In the overall term, those variables are now bound. See also | |
| -- 'Unbound.Generics.LocallyNameless.Operations.bind' and | |
| -- 'Unbound.Generics.LocallyNameless.Operations.unbind' and | |
| -- 'Unbound.Generics.LocallyNameless.Operations.lunbind' | |
| data Bind p t = B p t | |
| deriving (Generic) | |
| instance (Show p, Show t) => Show (Bind p t) where | |
| showsPrec prec (B p t) = | |
| showParen (prec > 0) (showString "<" | |
| . showsPrec prec p | |
| . showString "> " | |
| . showsPrec 0 t) | |
| instance (Alpha p, Alpha t) => Alpha (Bind p t) where | |
| aeq' ctx (B p1 t1) (B p2 t2) = | |
| aeq' (patternCtx ctx) p1 p2 | |
| && aeq' (incrLevelCtx ctx) t1 t2 | |
| isPat _ = inconsistentDisjointSet | |
| isTerm (B p t) = isConsistentDisjointSet (isPat p) && isTerm t | |
| nthPatFind b = error $ "Binding " ++ show b ++ " used as a pattern" | |
| namePatFind b = error $ "Binding " ++ show b ++ " used as a pattern" | |
| swaps' ctx perm (B p t) = | |
| B (swaps' (patternCtx ctx) perm p) | |
| (swaps' (incrLevelCtx ctx) perm t) | |
| -- | @Embed@ allows for terms to be /embedded/ within patterns. Such | |
| -- embedded terms do not bind names along with the rest of the | |
| -- pattern. For examples, see the tutorial or examples directories. | |
| -- | |
| -- If @t@ is a /term type/, then @Embed t@ is a /pattern type/. | |
| -- | |
| -- @Embed@ is not abstract since it involves no binding, and hence | |
| -- it is safe to manipulate directly. To create and destruct | |
| -- @Embed@ terms, you may use the @Embed@ constructor directly. | |
| -- (You may also use the functions 'embed' and 'unembed', which | |
| -- additionally can construct or destruct any number of enclosing | |
| -- 'Shift's at the same time.) | |
| newtype Embed t = Embed t deriving (Eq, Generic) | |
| instance Show a => Show (Embed a) where | |
| showsPrec _ (Embed a) = showString "{" . showsPrec 0 a . showString "}" | |
| instance Alpha t => Alpha (Embed t) where | |
| isPat (Embed t) = if (isTerm t) then mempty else inconsistentDisjointSet | |
| isTerm _ = False | |
| isEmbed (Embed t) = isTerm t | |
| swaps' ctx perm (Embed t) = | |
| if isTermCtx ctx | |
| then Embed t | |
| else Embed (swaps' (termCtx ctx) perm t) | |
| aeq' ctx (Embed x) (Embed y) = aeq' (termCtx ctx) x y | |
| nthPatFind _ = Left | |
| namePatFind _ _ = Left 0 | |
| -- | Some 'Alpha' operations need to record information about their | |
| -- progress. Instances should just pass it through unchanged. | |
| -- | |
| -- The context records whether we are currently operating on terms or patterns, | |
| -- and how many binding levels we've descended. | |
| data AlphaCtx = AlphaCtx { ctxMode :: !Mode, ctxLevel :: !Integer } | |
| newtype Perm a = Perm { applyPerm :: a -> a } | |
| instance Eq a => Monoid (Perm a) where | |
| mempty = Perm id | |
| mappend p1 p2 = undefined | |
| single :: a -> a -> Perm a | |
| single x y = undefined | |
| data Mode = Term | Pat | |
| deriving Eq | |
| -- | The starting context for alpha operations: we are expecting to | |
| -- work on terms and we are under no binders. | |
| initialCtx :: AlphaCtx | |
| initialCtx = AlphaCtx { ctxMode = Term, ctxLevel = 0 } | |
| -- | Switches to a context where we expect to operate on patterns. | |
| patternCtx :: AlphaCtx -> AlphaCtx | |
| patternCtx ctx = ctx { ctxMode = Pat } | |
| -- | Switches to a context where we expect to operate on terms. | |
| termCtx :: AlphaCtx -> AlphaCtx | |
| termCtx ctx = ctx { ctxMode = Term } | |
| -- | Returns 'True' iff we are in a context where we expect to see terms. | |
| isTermCtx :: AlphaCtx -> Bool | |
| isTermCtx (AlphaCtx {ctxMode = Term}) = True | |
| isTermCtx _ = False | |
| -- | Increment the number of binders that we are operating under. | |
| incrLevelCtx :: AlphaCtx -> AlphaCtx | |
| incrLevelCtx ctx = ctx { ctxLevel = 1 + ctxLevel ctx } | |
| -- | Decrement the number of binders that we are operating under. | |
| decrLevelCtx :: AlphaCtx -> AlphaCtx | |
| decrLevelCtx ctx = ctx { ctxLevel = ctxLevel ctx - 1 } | |
| -- | Are we operating under no binders? | |
| isZeroLevelCtx :: AlphaCtx -> Bool | |
| isZeroLevelCtx ctx = ctxLevel ctx == 0 | |
| -- | A @DisjointSet a@ is a 'Just' a list of distinct @a@s. In addition to a monoidal | |
| -- structure, a disjoint set also has an annihilator 'inconsistentDisjointSet'. | |
| -- | |
| -- @ | |
| -- inconsistentDisjointSet \<> s == inconsistentDisjointSet | |
| -- s \<> inconsistentDisjoinSet == inconsistentDisjointSet | |
| -- @ | |
| newtype DisjointSet a = DisjointSet (Maybe [a]) | |
| instance Eq a => Monoid (DisjointSet a) where | |
| mempty = DisjointSet (Just []) | |
| mappend s1 s2 = | |
| case (s1, s2) of | |
| (DisjointSet (Just xs), DisjointSet (Just ys)) | disjointLists xs ys -> DisjointSet (Just (xs <> ys)) | |
| _ -> inconsistentDisjointSet | |
| instance Foldable DisjointSet where | |
| foldMap summarize (DisjointSet ms) = foldMap (foldMap summarize) ms | |
| -- | Returns a @DisjointSet a@ that is the annihilator element for the 'Monoid' instance of 'DisjointSet'. | |
| inconsistentDisjointSet :: DisjointSet a | |
| inconsistentDisjointSet = DisjointSet Nothing | |
| -- | @singletonDisjointSet x@ a @DisjointSet a@ that contains the single element @x@ | |
| singletonDisjointSet :: a -> DisjointSet a | |
| singletonDisjointSet x = DisjointSet (Just [x]) | |
| disjointLists :: Eq a => [a] -> [a] -> Bool | |
| disjointLists xs ys = null (intersect xs ys) | |
| -- | @isConsistentDisjointSet@ returns @True@ iff the given disjoint set is not inconsistent. | |
| isConsistentDisjointSet :: DisjointSet a -> Bool | |
| isConsistentDisjointSet (DisjointSet Nothing) = False | |
| isConsistentDisjointSet _ = True | |
| -- | | |
| -- Module : Calc | |
| -- Copyright : (c) 2014, Aleksey Kliger | |
| -- License : BSD3 (See LICENSE) | |
| -- Maintainer : Aleksey Kliger | |
| -- Stability : experimental | |
| -- | |
| {-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-} | |
| module Calc where | |
| import Control.Arrow (second) | |
| import Data.Typeable (Typeable) | |
| import GHC.Generics (Generic) | |
| import Alpha | |
| -- variables will range over expressions | |
| type Var = Name Expr | |
| -- expression is either a variable, a constant int, a summation of two | |
| -- expressions, a list of variables bound to expressions that may | |
| -- occur in the body of an expression (where the expressions in the | |
| -- list of bindings refer to an outer scope), or a sequence of nested bindings | |
| -- where each binding expression can refer to previously bound variables. | |
| data Expr = V Var | |
| | C Int | |
| | Add Expr Expr | |
| | Lam (Bind Var Expr) | |
| | App Expr Expr | |
| | Let (Bind [(Var, Embed Expr)] Expr) | |
| | Case Expr [Match] | |
| deriving (Generic, Typeable, Show) | |
| data Match = Match (Bind Var Expr) | |
| deriving (Generic, Typeable, Show) | |
| instance Alpha Expr | |
| instance Alpha Match |