packages feed

algebra-checkers-0.1.0.0: src/AlgebraCheckers/Theorems.hs

{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE TupleSections         #-}

module AlgebraCheckers.Theorems where

import Data.Either
import Control.Monad
import Data.Bool
import Data.Char
import Data.Function (on)
import Data.Generics.Schemes (listify)
import Data.List (nub, (\\))
import Data.Maybe (isNothing, fromMaybe, mapMaybe)
import Data.Semigroup
import Language.Haskell.TH hiding (ppr, Arity)
import Language.Haskell.TH.Syntax (Module)
import Prelude hiding (exp)
import AlgebraCheckers.Homos
import AlgebraCheckers.Suggestions
import AlgebraCheckers.Types
import AlgebraCheckers.Unification


sanityCheck :: Module -> Theorem -> Bool
sanityCheck md = isNothing . sanityCheck' md

sanityCheck' :: Module -> Theorem -> Maybe TheoremProblem
sanityCheck' md (Law _ lhs rhs) =
  either Just (const Nothing) $ foldr1 (>>)
    [ lift_error (Contradiction . UnknownConstructors)
        $ fmap (\(UnboundVarE n) -> n)
        $ listify is_unbound_ctor (lhs, rhs)
    , ensure_bound_matches lhs rhs
    , ensure_bound_matches rhs lhs
    , bool (Left $ Contradiction UnequalValues) (Right ()) $
        on (&&) isFullyMatchable lhs rhs `implies` (==) lhs rhs
    , bool (Left $ Contradiction UnequalValues) (Right ()) $ fromMaybe True $
        liftM2 (==) (matchableAppHead lhs) (matchableAppHead rhs)
    , bool (Right ()) (Left $ Dodgy SelfRecursive) $ nonlinearUse md lhs rhs
    , bool (Right ()) (Left $ Dodgy SelfRecursive) $ nonlinearUse md rhs lhs
    ]
  where
    is_unbound_ctor (UnboundVarE n) = isUpper . head $ nameBase n
    is_unbound_ctor _ = False

    ensure_bound_matches a b
      = lift_error (Contradiction . UnboundMatchableVars)
      $ filter (not . exists_in a)
      $ matchableMetaVars b
    lift_error _ [] = Right ()
    lift_error ctor x = Left $ ctor x
    exists_in exp var = not . null $ listify (== var) exp

implies :: Bool -> Bool -> Bool
implies p q = not p || q

matchableMetaVars :: Exp -> [Name]
matchableMetaVars (UnboundVarE n) = [n]
matchableMetaVars e =
  case matchableAppHead e of
    Just _ -> go e
    Nothing -> []
  where
    go (exp1 `AppE` exp2) =
      go exp1 ++ matchableMetaVars exp2
    go _ = []

isFullyMatchable :: Exp -> Bool
isFullyMatchable (ConE _)                 = True
isFullyMatchable (TupE es)                = all isFullyMatchable es
isFullyMatchable (ListE es)               = all isFullyMatchable es
isFullyMatchable (LitE _)                 = True
isFullyMatchable (UnboundVarE _)          = True
isFullyMatchable (AppE (UnboundVarE _) _) = False
isFullyMatchable (AppE exp1 exp2)         = isFullyMatchable exp1 && isFullyMatchable exp2
isFullyMatchable _                        = False

namedLawToEither :: NamedLaw -> Either (Law ()) (Law String)
namedLawToEither (Law (LawName n) a b) = Right (Law n a b)
namedLawToEither (Law LawNotDodgy a b) = Left (Law () a b)

theorize :: Module -> [NamedLaw] -> [Theorem]
theorize md named_laws =
  let (not_dodgy, laws) = partitionEithers $ fmap namedLawToEither named_laws
      law_defs = fmap (\t -> t { lawData = LawDefn $ lawData t }) laws
      sane_laws = filter (sanityCheck md) law_defs
      theorems = do
         l1@Law{lawData = LawDefn l1name} <- sane_laws
         l2@Law{lawData = LawDefn l2name} <- sane_laws
         guard $ l1 /= l2
         (lhs, rhs) <- criticalPairs l1 l2
         pure $ Law (Interaction l1name l2name) lhs rhs
   in (nub $ law_defs <> theorems) \\ fmap (\l -> l {lawData = LawDefn ""} ) not_dodgy

matchableAppHead :: Exp -> Maybe Name
matchableAppHead (ConE n)   = Just n
matchableAppHead (AppE f _) = matchableAppHead f
matchableAppHead _          = Nothing

nonlinearUse :: Module -> Exp -> Exp -> Bool
nonlinearUse md exp1 exp2 =
  let exp2s = mapMaybe (\exp -> splitApps exp) $ fmap seExp $ subexps exp2
   in any (\(apphead, exps) -> nonlinearFunc md apphead && any (equalUpToAlpha exp1) exps) exp2s

nonlinearFunc :: Module -> Name -> Bool
nonlinearFunc md name = not $ sameModule md name