hobbits-1.0: Data/Binding/Hobbits/Mb.hs
{-# LANGUAGE GADTs, TypeOperators, FlexibleInstances, OverlappingInstances #-}
-- |
-- Module : Data.Binding.Hobbits.Mb
-- Copyright : (c) 2011 Edwin Westbrook, Nicolas Frisby, and Paul Brauner
--
-- License : BSD3
--
-- Maintainer : emw4@rice.edu
-- Stability : experimental
-- Portability : GHC
--
-- This module defines multi-bindings as the type 'Mb', as well as a number of
-- operations on multi-bindings. See the paper E. Westbrook, N. Frisby,
-- P. Brauner, \"Hobbits for Haskell: A Library for Higher-Order Encodings in
-- Functional Programming Languages\" for more information.
module Data.Binding.Hobbits.Mb (
-- * Abstract types
Name(), -- hides Name implementation
Binding(), -- hides Binding implementation
Mb(), -- hides MultiBind implementation
-- * Multi-binding constructors
nu, emptyMb, nuMulti,
-- * Operations on multi-bindings
elimEmptyMb, combineMb, separateMb, mbToProxy,
-- * Queries on names
cmpName, mbCmpName, mbNameBoundP,
-- * type classes for lifting data out of bindings
Liftable(..), Liftable1(..), Liftable2(..),
-- * Optimized, safe multi-binding eliminators
mbList
) where
import Data.Type.List
import qualified Data.Type.List.Proof.Member as Mem
import qualified Data.Type.List.Proof.Append as App
import Data.Binding.Hobbits.Internal
import qualified Data.Binding.Hobbits.Internal as I
import Data.List (elemIndex)
import Unsafe.Coerce (unsafeCoerce)
-------------------------------------------------------------------------------
-- bindings
-------------------------------------------------------------------------------
-- | A @Binding@ is simply a multi-binding that binds one name
type Binding a = Mb (Nil :> a)
instance Show (Name a) where
showsPrec _ (MkName n) = showChar '#' . shows n . showChar '#'
-- note: we reverse l to show the inner-most bindings last
instance Show a => Show (Mb c a) where
showsPrec p (MkMb l b) = showParen (p > 10) $
showChar '#' . shows (reverse l) . showChar '.' . shows b
-- README: we pass f to fresh_name to avoid let-floating the results
-- of fresh_name (which would always give the same name for each nu)
-- README: is *is* ok to do CSE on multiple copies of nu, because
-- the programmer cannot tell if two distinct (non-nested) nus get the
-- same fresh integer, and two nus that look the same to the CSE engine
-- cannot be nested
-- README: it *is* ok to inline nu because we don't care in
-- what order fresh names are created
{-|
@nu f@ creates a binding which binds a fresh name @n@ and whose
body is the result of @f n@.
-}
nu :: (Name a -> b) -> Binding a b
nu f = let n = fresh_name f in n `seq` MkMb [n] (f (MkName n))
-- README: inner-most bindings come FIRST
-- | Combines a binding inside another binding into a single binding.
combineMb :: Mb c1 (Mb c2 b) -> Mb (c1 :++: c2) b
combineMb (MkMb l1 (MkMb l2 b)) = MkMb (l2 ++ l1) b
{-|
Separates a binding into two nested bindings. The first argument, of
type @'Append' c1 c2 c@, is a \"phantom\" argument to indicate how
the context @c@ should be split.
-}
separateMb :: Append c1 c2 c -> Mb c a -> Mb c1 (Mb c2 a)
separateMb app (MkMb l a) = MkMb d (MkMb t a) where
(d, t) = splitAt (App.length app) l
-- | Returns a proxy object that enumerates all the types in ctx.
mbToProxy :: Mb ctx a -> MapC Proxy ctx
mbToProxy (MkMb l _) = unsafeProxyFromLen (length l)
-- | Creates an empty binding that binds 0 names.
emptyMb :: a -> Mb Nil a
emptyMb t = MkMb [] t
{-|
The expression @nuMulti p f@ creates a multi-binding of zero or more
names, one for each element of the vector @p@. The bound names are
passed the names to @f@, which returns the body of the
multi-binding. The argument @p@, of type @'Mb' f ctx@, acts as a
\"phantom\" argument, used to reify the list of types @ctx@ at the
term level; thus it is unimportant what the type function @f@ is.
-}
nuMulti :: MapC f ctx -> (MapC Name ctx -> b) -> Mb ctx b
nuMulti Nil f = emptyMb $ f Nil
nuMulti (proxies :> proxy) f =
combineMb $ nuMulti proxies $ \names -> nu $ \n -> f (names :> n)
{-|
Eliminates an empty binding, returning its body. Note that
@elimEmptyMb@ is the inverse of @emptyMb@.
-}
elimEmptyMb :: Mb Nil a -> a
elimEmptyMb (MkMb [] t) = t
elimEmptyMb _ = error "Should not happen! (elimEmptyMb)"
{-|
@cmpName n m@ compares names @n@ and @m@ of types @Name a@ and @Name b@,
respectively. When they are equal, @Some e@ is returned for @e@ a proof
of type @a :=: b@ that their types are equal. Otherwise, @None@ is returned.
For example:
> nu $ \n -> nu $ \m -> cmpName n n == nu $ \n -> nu $ \m -> Some Refl
> nu $ \n -> nu $ \m -> cmpName n m == nu $ \n -> nu $ \m -> None
-}
cmpName :: Name a -> Name b -> Maybe (a :=: b)
cmpName (MkName n1) (MkName n2)
| n1 == n2 = Just $ unsafeCoerce Refl
| otherwise = Nothing
{-|
Checks if a name is bound in a multi-binding, returning @Left mem@
when the name is bound, where @mem@ is a proof that the type of the
name is in the type list for the multi-binding, and returning
@Right n@ when the name is not bound, where @n@ is the name.
For example:
> nu $ \n -> mbNameBoundP (nu $ \m -> m) == nu $ \n -> Left Member_Base
> nu $ \n -> mbNameBoundP (nu $ \m -> n) == nu $ \n -> Right n
-}
mbNameBoundP :: Mb ctx (Name a) -> Either (Member ctx a) (Name a)
mbNameBoundP (MkMb names (MkName n)) = case elemIndex n names of
Nothing -> Right (MkName n)
Just i -> Left (I.unsafeLookupC i)
{-|
Compares two names inside bindings, taking alpha-equivalence into
account; i.e., if both are the @i@th name, or both are the same name
not bound in their respective multi-bindings, then they compare as
equal. The return values are the same as for 'cmpName', so that
@Some Refl@ is returned when the names are equal and @Nothing@ is
returned when they are not.
-}
mbCmpName :: Mb c (Name a) -> Mb c (Name b) -> Maybe (a :=: b)
mbCmpName b1 b2 = case (mbNameBoundP b1, mbNameBoundP b2) of
(Left mem1, Left mem2) -> Mem.same mem1 mem2
(Right n1, Right n2) -> cmpName n1 n2
_ -> Nothing
{-|
The class @Liftable a@ gives a \"lifting function\" for a, which can
take any data of type @a@ out of a multi-binding of type @'Mb' ctx a@.
-}
class Liftable a where
mbLift :: Mb ctx a -> a
instance Liftable Int where
mbLift (MkMb _ i) = i
instance Liftable Char where
mbLift (MkMb _ c) = c
{-
instance Liftable a => Liftable [a] where
mbLift (MkMb _ []) = []
mbLift (MkMb names (x : xs)) =
(mbLift (MkMb names x)) : (mbLift (MkMb names xs))
-}
instance Liftable (Member c a) where
mbLift (MkMb _ mem) = mem
{-|
The class @Liftable1 f@ gives a lifting function for each type @f a@
when @a@ itself is @Liftable@.
-}
class Liftable1 f where
mbLift1 :: Liftable a => Mb ctx (f a) -> f a
instance (Liftable1 f, Liftable a) => Liftable (f a) where
mbLift = mbLift1
{-|
The class @Liftable2 f@ gives a lifting function for each type @f a b@
when @a@ and @b@ are @Liftable@.
-}
class Liftable2 f where
mbLift2 :: (Liftable a, Liftable b) => Mb ctx (f a b) -> f a b
instance (Liftable2 f, Liftable a) => Liftable1 (f a) where
mbLift1 = mbLift2
-- | Lift a list (but not its elements) out of a multi-binding
mbList :: Mb c [a] -> [Mb c a]
mbList (MkMb names []) = []
mbList (MkMb names (x : xs)) = (MkMb names x) : (mbList (MkMb names xs))