Skip to content

Instantly share code, notes, and snippets.

@lambdageek
Created May 14, 2015 19:33
Show Gist options
  • Save lambdageek/c05cd379fe649600070c to your computer and use it in GitHub Desktop.
Save lambdageek/c05cd379fe649600070c to your computer and use it in GitHub Desktop.
Example for GHC bug 10293

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment