packages feed

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

{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE TemplateHaskellQuotes #-}

module AlgebraCheckers.Suggestions where

import AlgebraCheckers.Patterns
import AlgebraCheckers.Ppr
import AlgebraCheckers.Unification
import Control.Monad
import Data.Char
import Data.Data
import Data.Generics.Schemes (listify)
import Data.Group
import Data.List
import Data.Maybe
import Data.Semigroup
import Data.Traversable
import Language.Haskell.TH hiding (ppr)
import Language.Haskell.TH.Syntax
import Prelude hiding (exp)
import THInstanceReification


data Suggestion
  = HomoSuggestion Name Name Int Type Type Exp
  deriving (Eq, Ord, Show)

homoSuggestionEq :: Suggestion -> Suggestion -> Bool
homoSuggestionEq (HomoSuggestion _ fn1 ix1 _ _ _)
                 (HomoSuggestion _ fn2 ix2 _ _ _) = fn1 == fn2
                                                 && ix1 == ix2


pprSuggestion :: Suggestion -> Doc
pprSuggestion (HomoSuggestion nm _ _ arg_ty res_ty (LamE [VarP var] exp)) =
  ppr $ deModuleName $
    VarE 'law `AppTypeE` ConT nm `AppE` (LamE [SigP (VarP var) arg_ty] $ SigE exp res_ty)
pprSuggestion (HomoSuggestion nm _ _ _ _ exp) =
  ppr $ deModuleName $
    VarE 'law `AppTypeE` ConT nm `AppE` exp


knownSuggestionHierarchies :: [[Name]]
knownSuggestionHierarchies =
  [ [ ''Group, ''Monoid, ''Semigroup ]
  ]

suggest :: Data a => Module -> a -> Q [Suggestion]
suggest md a = do
  let surface = getSurface md a
  fmap (join . join) $
    for surface $ \nm ->
      for knownSuggestionHierarchies $ \hierarchy -> do
        zs <- fmap join $ for hierarchy $ \tc_name -> do
          VarI _ ty _ <- reify nm
          possibleHomos tc_name nm ty
        pure $ nubBy homoSuggestionEq zs


suggest' :: Data a => a -> Q [Suggestion]
suggest' a = do
  md <- thisModule
  suggest md a



possibleHomos :: Name -> Name -> Type -> Q [Suggestion]
possibleHomos tc_name fn ty = do
  let (args, res) = unrollTyArr ty
  hasInstance tc_name res >>= \case
    False -> pure []
    True  -> do
      names <- for args $ newName . goodTyName
      fmap catMaybes $ for (zip3 names args [0..]) $ \(name, arg, ix) ->
        hasInstance tc_name arg >>= \case
          False -> pure Nothing
          True  -> do
            exp <- lamE [varP name] $ appsE $ varE fn : fmap varE names
            pure $ Just $ HomoSuggestion tc_name fn ix arg res exp


goodTyName :: Type -> String
goodTyName = fmap toLower . take 1 . dropWhile (not . isAlpha) . render . ppr . deModuleName

getSurface :: Data a => Module -> a -> [Name]
getSurface m = listify (sameModule m)


sameModule :: Module -> Name -> Bool
sameModule (Module (PkgName pkg) (ModName md)) n =
  nameModule n == Just md && namePackage n == Just pkg


unrollTyArr :: Type -> ([Type], Type)
unrollTyArr ty =
  let tys = unloopTyArrs ty
   in (init tys, last tys)
  where
    unloopTyArrs :: Type -> [Type]
    unloopTyArrs (ArrowT `AppT` a `AppT` b) =  a : unloopTyArrs b
    unloopTyArrs t =  [t]

hasInstance :: Name -> Type -> Q Bool
hasInstance tc_name = isProperInstance tc_name . pure