packages feed

lol-calculus-1.20160822: Language/LOL/Calculus/Term.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
module Language.LOL.Calculus.Term where

import Control.Applicative (Applicative(..), (<$>))
import Control.Monad
import Data.Bool
import Data.Eq (Eq(..))
import Data.Foldable (Foldable(..))
import Data.Function (($), (.))
import Data.Maybe (Maybe(..), maybe, catMaybes, fromJust)
import Data.Monoid (Monoid(..), (<>))
import Data.Ord (Ord(..))
import Data.Text.Buildable (Buildable(..))
import qualified Data.Text.Lazy.Builder as Builder
import Data.Traversable (Traversable(..))
import Data.Typeable as Typeable
import Text.Show (Show(..), showParen, showString)

-- import Debug.Trace

import Language.LOL.Calculus.Abstraction

-- * Type 'Term'
-- | 'Term' @var@ forms the main /Abstract Syntax Tree/
-- of the present /explicitely typed/ (aka. /à la Church/) /lambda-calculus/,
-- whose /term constructors/ are:
--
-- * 'TeTy_Var':      for /term variable/ or /type variable/, see 'Abstraction'.
-- * 'TeTy_App':      for /term application/ or /type application/, see 'normalize'.
-- * 'Term_Abst':     for /term abstraction/ (aka. /λ-term/), see 'Abstraction'.
-- * 'Type_Abst':     for /type abstraction/ (aka. /Π-type/, aka. /dependent product/), see 'sort_of_type_abst'.
-- * 'Type_Sort':     for /sort constant/ of a /Pure Type System/ (aka. /PTS/), see 'sort_of_sort'.
-- * 'TeTy_Axiom':    for /custom axiom/, see 'Axiomable'.
--
-- Note that 'Type' and 'Term' share this same data-type
-- (hence the varying prefixes of the Haskell /data constructors/),
-- which avoids duplication of code for their common operations.
--
-- Note that this Haskell type DOES NOT guarantee by itself
-- the /well-formedness/ of the 'Term' (or 'Type'),
-- for instance one can construct such @malformed_type@: @(* *)@:
--
-- @
-- > let star = 'Type_Sort' 'sort_star_mono'
-- > let malformed_type = 'TeTy_App' star star
-- > 'left' 'type_error_msg' '$' 'type_of' 'context' malformed_type
-- Left ('Type_Error_Msg_Not_a_function' … )
-- @
--
-- Though an Haskell type could be crafted to get a stronger guarantee
-- over the /well-formedness/, it is not done here
-- for the following reasons :
--
-- 1. The /well-formedness/ alone is not really useful,
-- it’s the /well-typedness/ which matters,
-- and this depends upon a specific 'Context'.
--
-- 2. Guaranteeing a good combinaison of 'TeTy_App' with respect to 'Term_Abst' (or 'Type_Abst')
-- while using the 'Abstraction' approach could be done:
-- for instance by using @GADTs@ on 'Term'
-- to add an Haskell /type parameter/ @ty@ (aka. /index/)
-- constrained to @(i -> o)@, @i@ or @o@ depending on the /term constructors/,
-- and then by defining Haskell /type classes/
-- replicating: 'Functor', 'Foldable', 'Traversable', 'Monad' and 'Monad_Module_Left',
-- but working on /natural transformations/ (giving /indexed functors/, /indexed monads/, …),
-- however this yields to a significant increase in code complexity,
-- which is not really worth the resulting guarantee
-- (to my mind, here, simplicity has priority
-- over comprehensive automated checking,
-- especially since it’s a research program
-- where I don't fully know all what will be needed
-- and thus appreciate some level of flexibility).
--
-- So, with this actual 'Term' data-type:
-- 'type_of' MUST be used to check the /well-formedness/ along the /well-typedness/
-- of a 'Term' (or equivalently of a 'Type') with respect to a 'Context'.
data Term var
 =   TeTy_Var  var
 |   TeTy_App  (Term var) (Term var)
 |   Term_Abst (Suggest Var_Name) (Type var) (Abstraction (Suggest Var_Name) Term var)
 |   Type_Abst (Suggest Var_Name) (Type var) (Abstraction (Suggest Var_Name) Type var)
 |   Type_Sort Sort
 |   forall ax .
     ( Axiomable (Axiom ax)
     , Typeable ax
     ) => TeTy_Axiom (Axiom ax)

-- * Type 'Type'

-- | 'Type' and 'Term' share the same data-type.
type Type = Term

-- | 'Eq' instance ignores /bound variables/' 'Var_Name',
-- effectively testing for /α-equivalence/.
instance (Eq var, Show var) => Eq (Term var) where
	TeTy_Var x         == TeTy_Var y         = x   == y
	TeTy_App xf xa     == TeTy_App yf ya     = xf  == yf  && xa == ya
	Term_Abst _ xty xf == Term_Abst _ yty yf = xty == yty && xf == yf -- NOTE: ignore Var_Name
	Type_Abst _ xty xf == Type_Abst _ yty yf = xty == yty && xf == yf -- NOTE: ignore Var_Name
	Type_Sort x        == Type_Sort y        = x   == y
	TeTy_Axiom x       == TeTy_Axiom y       = x `axiom_eq` y
	_ == _ = False
deriving instance Show var => Show (Term var)
-- | A 'Functor' instance capturing the notion of /variable renaming/.
deriving instance Functor Term
deriving instance Foldable Term
deriving instance Traversable Term
deriving instance Typeable Term
instance Show1 Term where showsPrec1 = showsPrec
instance Eq1   Term where (==#) = (==)

instance Applicative Term where
	pure = TeTy_Var
	(<*>) = ap
-- | A 'Monad' instance capturing the notion of /variable substitution/ with /capture-avoiding/.
instance Monad Term where
	return = pure
	Type_Sort        s >>= _  = Type_Sort s
	TeTy_Axiom       a >>= _  = TeTy_Axiom a
	TeTy_Var         v >>= go = go v
	TeTy_App    f    x >>= go = TeTy_App    (f    >>= go) (x  >>= go)
	Term_Abst v f_in f >>= go = Term_Abst v (f_in >>= go) (f >>>= go)
	Type_Abst v f_in f >>= go = Type_Abst v (f_in >>= go) (f >>>= go)
instance Buildable var => Buildable (Term var) where
	build = go False False
		where
		go :: forall v. (Buildable v)
		 => Bool -> Bool
		 -> Term v
		 -> Builder.Builder
		go parenBind parenApp te =
			case te of
			 Type_Sort s -> build s
			 TeTy_Axiom ax -> build ax
			 TeTy_Var v -> build v
			 TeTy_App f x ->
				(if parenApp then "(" else "")
				<> go True False f <> " " <> go True True x
				<> (if parenApp then ")" else "")
			 Term_Abst{} ->
				(if parenBind then "(" else "")
				<> "λ"{- <> "\\"-} <> go_abst parenBind parenApp te
			 Type_Abst (Suggest x) f_in f_out ->
				(if parenBind then "(" else "")
				<> (if x == ""
					then go True  False f_in
					else "∀" <> "(" <> build x <> ":" <> go False False f_in <> ")" )
				<> " -> "
				<> go False False (abstract_normalize f_out)
				<> (if parenBind then ")" else "")
		go_abst :: forall v. (Buildable v)
		 => Bool -> Bool
		 -> Term v
		 -> Builder.Builder
		go_abst parenBind parenApp te =
			case te of
			 Term_Abst (Suggest x) f_in f ->
				let body = abstract_normalize f in
				case body of
				 Term_Abst{} ->
					"(" <> go_var_def x <> ":" <> go False False f_in <> ")"
					<> " " <> go_abst parenBind parenApp body
				 _ ->
					"(" <> go_var_def x <> ":" <> go False False f_in <> ")"
					<> " -> "
					<> go False False body
					<> (if parenBind then ")" else "")
			 _ -> go parenBind parenApp te
		go_var_def x = if x == "" then "_" else build x

-- ** Type 'Sort'
-- | Four /PTS/ /sort constants/
-- are formed by combining
-- 'Type_Level' and 'Type_Morphism':
--
-- * @*m@: the 'Sort' to form the 'Type' of a monomorphic 'Term'.
-- * @*p@: the 'Sort' to form the 'Type' of a polymorphic 'Term'.
-- * @□m@: the 'Sort' to form the 'Type' of a monomorphic 'Type'.
-- * @□p@: the 'Sort' to form the 'Type' of a polymorphic 'Type'.
type Sort = (Type_Level, Type_Morphism)
instance Buildable Sort where
	build x = case x of
	 (sort, morphism) -> build sort <> build morphism

-- *** Type 'Type_Level'
data Type_Level
 =   Type_Level_0 -- ^ aka. /@*@ (Star) sort constant/.
 |   Type_Level_1 -- ^ aka. /@□@ (Box)  sort constant/.
 deriving (Eq, Ord, Show)
instance Buildable Type_Level where
	build x = case x of
	 Type_Level_0 -> "*"
	 Type_Level_1 -> "□"

-- | @*m@: the 'Sort' to form the 'Type' of a monomorphic 'Term'.
sort_star_mono :: Sort
sort_star_mono = (Type_Level_0, Type_Morphism_Mono)

-- | @*p@: the 'Sort' to form the 'Type' of a polymorphic 'Term'.
sort_star_poly :: Sort
sort_star_poly = (Type_Level_0, Type_Morphism_Poly)

-- *** Type 'Type_Morphism'
data Type_Morphism
 =   Type_Morphism_Mono
 |   Type_Morphism_Poly
 deriving (Eq, Ord, Show)
instance Buildable Type_Morphism where
	build x = case x of
	 Type_Morphism_Mono -> "" -- "m"
	 Type_Morphism_Poly -> "p"

-- * Type 'Axiom'

data family Axiom r

-- ** Class 'Axiomable'

-- | Instances of this class are 'Axiom's injectable in 'TeTy_Axiom', that is:
--
-- * they have a 'Type', given by 'axiom_type_of',
-- * and they can perform an optional reduction
--   within 'normalize' or 'whnf', given by 'axiom_normalize'.
class
 ( Eq ax, Show ax, Buildable ax, Typeable ax
 ) => Axiomable ax where
	axiom_type_of
	 :: forall var. Typeable var
	 => Context var -> ax -> Type var
	 -- ^ Return the 'Type' of the given 'Axiomable' instance.
	axiom_normalize
	 :: forall var. (Typeable var, Ord var, Variable var)
	 => Context var -> ax -> [Term var] -> Maybe (Term var, [Term var])
	 -- ^ Custom-'normalize': given a typing 'Context',
	 -- an 'Axiomable' instance
	 -- and a list of arguments, return:
	 --
	 -- * 'Nothing': if the axiom performs no reduction,
	 -- * 'Just': with the reducted 'Term' and the arguments not used by the reduction.
	 --
	 -- Default: @\\_ctx _ax _args -> Nothing@
	axiom_normalize _ctx _ax _args = Nothing
	axiom_eq :: ax -> (forall ax'. Axiomable ax' => ax' -> Bool)
	 -- ^ Custom-bipolymorphic-@(==)@:
	 -- given an 'Axiomable' instance
	 -- return a function to test if any
	 -- other 'Axiomable' instance
	 -- is equal to the first instance given.
	 --
	 -- Default: @maybe False (x ==) (cast y)@
	axiom_eq x y = maybe False (x ==) (cast y)

-- * Type 'Form'

-- | A record to keep a same 'Term' (or 'Type')
-- in several forms (and avoid to 'normalize' it multiple times).
data Form var
 =   Form
 {   form_given  :: Term var
 ,   form_normal :: Term var -- ^ 'normalize'-d version of 'form_given'.
 } deriving (Functor, Show)

-- * Type 'Context'

-- | Map variables of type @var@
-- to their 'Type' and maybe also to their 'Term',
-- both in 'form_given' and 'form_normal'.
data Context var
 =   Context
 {   context_vars   :: [var]
  -- ^ used to dump the 'Context'
 ,   context_lookup :: var -> Maybe (Context_Item var)
  -- ^ used to query the 'Context'
 ,   context_lift   :: Typeable var => Var_Name -> var
  -- ^ used to promote a ('Term' 'Var_Name') to ('Term' @var@)
 ,   context_unlift :: Typeable var => var -> Var_Name
  -- ^ used to unpromote ('Term' @var@) to ('Term' 'Var_Name')
 }
data Context_Item var
 =   Context_Item
 {   context_item_term :: Maybe (Form var)
 ,   context_item_type :: Form var
 } deriving (Functor, Show)

instance Show var => Show (Context var) where
	showsPrec n ctx@Context{context_vars=vars} =
		showParen (n > 10) $
		showString "Context " .
		showsPrec n ((\k ->
			(k, fromJust $ context_item_type
			 <$> context_lookup ctx k))
		 <$> vars) .
		showString " " .
		showsPrec n (catMaybes $ (\k ->
			(k,) . form_given <$>
			(context_item_term =<< context_lookup ctx k))
		 <$> vars)
instance Buildable var => Buildable (Context var) where
	build ctx@Context{context_vars=vars} =
		foldMap (\var ->
			"  " <> build var
			 <> maybe mempty (\ty -> " : " <> build (form_given ty))
				 (context_item_type <$> context_lookup ctx var)
			 {-
			 <> maybe mempty (\te -> " = " <> build (form_given te))
				 (context_item_term =<< context_lookup ctx var)
			 -}
			 <> "\n"
		 ) vars