packages feed

hydra-0.8.0: src/main/haskell/Hydra/Inference/Rules.hs

-- | Inference rules

module Hydra.Inference.Rules where

import Hydra.Basics
import Hydra.Strip
import Hydra.Compute
import Hydra.Core
import Hydra.Schemas
import Hydra.CoreEncoding
import Hydra.Graph
import Hydra.Lexical
import Hydra.Mantle
import Hydra.Rewriting
import Hydra.Inference.Substitution
import Hydra.Unification
import Hydra.Tools.Debug
import Hydra.Annotations
import Hydra.Tier1
import Hydra.Tier2
import qualified Hydra.Dsl.Types as Types

import qualified Control.Monad as CM
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y


data Inferred a = Inferred {
  -- The original term, possibly annotated with the inferred type
  inferredObject :: a,
  -- The inferred type
  inferredType :: Type,
  -- Any constraints introduced by the inference process
  inferredConstraints :: [TypeConstraint]
} deriving Show

key_vcount = Name "vcount"

constraint :: Type -> Type -> TypeConstraint
constraint t1 t2 = TypeConstraint t1 t2 Nothing

fieldType :: Field -> FieldType
fieldType (Field fname term) = FieldType fname $ termType term

findMatchingField :: Name -> [FieldType] -> Flow Graph FieldType
findMatchingField fname sfields = case L.filter (\f -> fieldTypeName f == fname) sfields of
  []    -> fail $ "no such field: " ++ unName fname
  (h:_) -> return h

freshName :: Flow Graph Name
freshName = normalVariable <$> nextCount key_vcount

freshVariableType :: Flow Graph Type
freshVariableType = TypeVariable <$> freshName

generalize :: M.Map Name TypeScheme -> Type -> TypeScheme
generalize env t  = TypeScheme vars t
  where
    vars = S.toList $ S.difference
      (freeVariablesInType t)
      (L.foldr (S.union . freeVariablesInScheme) S.empty $ M.elems env)

infer :: Term -> Flow Graph (Inferred Term)
infer term = withTrace ("infer for " ++ show (termVariant term)) $ case term of
    TermAnnotated (AnnotatedTerm term1 ann) -> do
      (Inferred term2 typ constraints) <- infer term1
      return (Inferred (TermAnnotated $ AnnotatedTerm term2 ann) typ constraints)

    TermApplication (Application fun arg) -> do
      (Inferred ifun ityp funconst) <- infer fun
      (Inferred iarg atyp argconst) <- infer arg
      cod <- freshVariableType
      let constraints = funconst ++ argconst ++ [constraint ityp $ Types.function atyp cod]
      yield (TermApplication $ Application ifun iarg) cod constraints

    TermFunction f -> case f of

      FunctionElimination e -> case e of

        EliminationList fun -> do
          a <- freshVariableType
          b <- freshVariableType
          let expected = Types.functionN [b, a, b]
          (Inferred i t c) <- infer fun
          let elim = Types.functionN [b, Types.list a, b]
          yieldElimination (EliminationList i) elim (c ++ [constraint expected t])

        EliminationOptional (OptionalCases n j) -> do
          dom <- freshVariableType
          cod <- freshVariableType
          (Inferred ni nt nconst) <- infer n
          (Inferred ji jt jconst) <- infer j
          let t = Types.function (Types.optional dom) cod
          let constraints = nconst ++ jconst
                              ++ [constraint cod nt, constraint (Types.function dom cod) jt]
          yieldElimination (EliminationOptional $ OptionalCases ni ji) t constraints

        EliminationProduct (TupleProjection arity idx) -> do
          types <- CM.replicateM arity freshVariableType
          let cod = types !! idx
          let t = Types.function (Types.product types) cod
          yieldElimination (EliminationProduct $ TupleProjection arity idx) t []

        EliminationRecord (Projection name fname) -> do
          rt <- requireRecordType name
          sfield <- findMatchingField fname (rowTypeFields rt)
          yieldElimination (EliminationRecord $ Projection name fname)
            (Types.function (TypeRecord rt) $ fieldTypeType sfield) []

        EliminationUnion (CaseStatement tname def cases) -> do
            -- Default value
            (idef, dfltConstraints) <- case def of
              Nothing -> pure (Nothing, [])
              Just d -> do
                (Inferred i _ c) <- infer d
                return (Just i, c)

            -- Cases
            icases' <- CM.mapM inferFieldType cases
            let icases = inferredObject <$> icases'
            let casesconst = inferredConstraints <$> icases'
            let icasesMap = fieldMap icases
            rt <- requireUnionType tname
            let sfields = fieldTypeMap  $ rowTypeFields rt
            checkCasesAgainstSchema tname icasesMap sfields
            let pairMap = productOfMaps icasesMap sfields

            cod <- freshVariableType
            let outerConstraints = (\(d, s) -> constraint (termType d) $ Types.function s cod) <$> M.elems pairMap
            let innerConstraints = dfltConstraints ++ L.concat casesconst

            yieldElimination (EliminationUnion (CaseStatement tname idef icases))
              (Types.function (TypeUnion rt) cod)
              (innerConstraints ++ outerConstraints)
          where
            checkCasesAgainstSchema tname icases sfields = if M.null diff
                then pure ()
                else fail $ "case(s) in case statement which do not exist in type " ++ unName tname ++ ": "
                  ++ L.intercalate ", " (unName <$> M.keys diff)
              where
                diff = M.difference icases sfields

        EliminationWrap name -> do
          typ <- requireWrappedType name
          yieldElimination (EliminationWrap name) (Types.function (TypeWrap $ WrappedType name typ) typ) []

      FunctionLambda (Lambda v _ body) -> do
        tv <- freshVariableType
        (Inferred i t iconst) <- withBinding v (monotype tv) $ infer body
        yieldFunction (FunctionLambda $ Lambda v (Just tv) i) (Types.function tv t) iconst

      FunctionPrimitive name -> do
          ts <- (typeOfPrimitive name) >>= instantiate
          -- TODO: propagate the entire type scheme, not only the body
          yieldFunction (FunctionPrimitive name) (typeSchemeType ts) []

    TermLet lt -> inferLet lt

    TermList els -> do
        v <- freshVariableType
        if L.null els
          then yield (TermList []) (Types.list v) []
          else do
            iels' <- CM.mapM infer els
            let iels = inferredObject <$> iels'
            let elsconst = inferredConstraints <$> iels'
            let co = (\e -> constraint v $ termType e) <$> iels
            let ci = L.concat elsconst
            yield (TermList iels) (Types.list v) (co ++ ci)

    TermLiteral l -> yield (TermLiteral l) (Types.literal $ literalType l) []

    TermMap m -> do
        kv <- freshVariableType
        vv <- freshVariableType
        if M.null m
          then yield (TermMap M.empty) (Types.map kv vv) []
          else do
            triples <- CM.mapM toTriple $ M.toList m
            let pairs = (\(k, v, _) -> (k, v)) <$> triples
            let co = L.concat ((\(k, v, c) -> c ++ [constraint kv $ termType k, constraint vv $ termType v]) <$> triples)
            yield (TermMap $ M.fromList pairs) (Types.map kv vv) co
      where
        toTriple (k, v) = do
          (Inferred ik _ kc) <- infer k
          (Inferred iv _ vc) <- infer v
          return (ik, iv, kc ++ vc)

    TermOptional m -> do
      v <- freshVariableType
      case m of
        Nothing -> yield (TermOptional Nothing) (Types.optional v) []
        Just e -> do
          (Inferred i t ci) <- infer e
          yield (TermOptional $ Just i) (Types.optional v) ((constraint v t):ci)

    TermProduct tuple -> do
      is' <- CM.mapM infer tuple
      let is = inferredObject <$> is'
      let co = L.concat (inferredConstraints <$> is')
      yield (TermProduct is) (TypeProduct $ fmap termType is) co

    TermRecord (Record n fields) -> do
        rt <- requireRecordType n
        ifields' <- CM.mapM inferFieldType fields
        let ifields = inferredObject <$> ifields'
        let ci = L.concat (inferredConstraints <$> ifields')
        let irt = TypeRecord $ RowType n (fieldType <$> ifields)
        yield (TermRecord $ Record n ifields) irt ((constraint irt $ TypeRecord rt):ci)

    TermSet els -> do
      v <- freshVariableType
      if S.null els
        then yield (TermSet S.empty) (Types.set v) []
        else do
          iels' <- CM.mapM infer $ S.toList els
          let iels = inferredObject <$> iels'
          let co = (\e -> (constraint v $ termType e)) <$> iels
          let ci = L.concat (inferredConstraints <$> iels')
          yield (TermSet $ S.fromList iels) (Types.set v) (co ++ ci)

    TermSum (Sum i s trm) -> do
        (Inferred it t co) <- infer trm
        types <- CM.sequence (varOrTerm t <$> [0..(s-1)])
        yield (TermSum $ Sum i s it) (TypeSum types) co
      where
        varOrTerm t j = if i == j
          then pure t
          else freshVariableType

    TermTyped (TypedTerm term1 typ) -> do
      (Inferred i t c) <- infer term1
      return $ Inferred (setTermType (Just typ) i) typ $ c ++ [constraint typ t]

    TermUnion (Injection n field) -> do
        rt <- requireUnionType n
        sfield <- findMatchingField (fieldName field) (rowTypeFields rt)
        (Inferred ifield t ci) <- inferFieldType field
        let co = constraint t $ fieldTypeType sfield

        yield (TermUnion $ Injection n ifield) (TypeUnion rt) (co:ci)

    TermVariable v -> do
      ts <- requireName v
      -- TODO: propagate the entire type scheme, not only the body
      yield (TermVariable v) (typeSchemeType ts) []

    TermWrap (WrappedTerm name term1) -> do
      typ <- requireWrappedType name
      (Inferred i t ci) <- infer term1
      yield (TermWrap $ WrappedTerm name i) (TypeWrap $ WrappedType name typ) (ci ++ [constraint typ t])

inferFieldType :: Field -> Flow Graph (Inferred Field)
inferFieldType (Field fname term) = do
  (Inferred i t c) <- infer term
  return (Inferred (Field fname i) t c)

inferLet :: Let -> Flow Graph (Inferred Term)
inferLet (Let bindings env) = withTrace ("let(" ++ L.intercalate "," (unName . letBindingName <$> bindings) ++ ")") $ do
    state0 <- getState
    let e = preExtendEnv bindings $ graphTypes state0
    let state1 = state0 {graphTypes = e}
    withState state1 $ do
      -- TODO: perform a topological sort on the bindings; this process should be unified with that of elements in a graph

      -- Infer types of bindings in the pre-extended environment
      ivalues' <- CM.mapM infer (letBindingTerm <$> bindings)
      let ivalues = inferredObject <$> ivalues'
      let ibindings = L.zipWith (\(LetBinding k v t) i -> LetBinding k i t) bindings ivalues
      let bc = L.concat (inferredConstraints <$> ivalues')

      let tbindings = M.fromList $ fmap (\(LetBinding k i t) -> (k, termTypeScheme i)) ibindings
      (Inferred ienv t cenv) <- withBindings tbindings $ infer env

      yield (TermLet $ Let ibindings ienv) t (bc ++ cenv)
  where
    -- Add any manual type annotations for the bindings to the environment, enabling type inference over recursive definitions
    preExtendEnv bindings e = foldl addPair e bindings
      where
        addPair e (LetBinding name term _) = case typeOfTerm term of
          Nothing -> e
          Just typ -> M.insert name (monotype typ) e

instantiate :: TypeScheme -> Flow Graph TypeScheme
instantiate (TypeScheme vars t) = do
    vars1 <- mapM (const freshName) vars
    return $ TypeScheme vars1 $ substituteInType (M.fromList $ L.zip vars (TypeVariable <$> vars1)) t

monotype :: Type -> TypeScheme
monotype typ = TypeScheme [] typ

productOfMaps :: Ord k => M.Map k l -> M.Map k r -> M.Map k (l, r)
productOfMaps ml mr = M.fromList $ Y.catMaybes (toPair <$> M.toList mr)
  where
    toPair (k, vr) = (\vl -> (k, (vl, vr))) <$> M.lookup k ml

reduceType :: Type -> Type
reduceType t = t -- betaReduceType cx t

requireName :: Name -> Flow Graph TypeScheme
requireName v = do
  env <- graphTypes <$> getState
  case M.lookup v env of
    Nothing -> fail $ "variable not bound in environment: " ++ unName v ++ ". Environment: "
      ++ L.intercalate ", " (unName <$> M.keys env)
    Just s -> instantiate s

termType :: Term -> Type
termType term = case stripTerm term of
  (TermTyped (TypedTerm _ typ)) -> typ

-- TODO: limited and temporary
termTypeScheme :: Term -> TypeScheme
termTypeScheme = monotype . termType

typeOfPrimitive :: Name -> Flow Graph TypeScheme
typeOfPrimitive name = primitiveType <$> requirePrimitive name

typeOfTerm :: Term -> Maybe Type
typeOfTerm term = case term of
  TermAnnotated (AnnotatedTerm term1 _) -> typeOfTerm term1
  TermTyped (TypedTerm term1 typ) -> Just typ
  _ -> Nothing

withBinding :: Name -> TypeScheme -> Flow Graph x -> Flow Graph x
withBinding n ts = withEnvironment (M.insert n ts)

withBindings :: M.Map Name TypeScheme -> Flow Graph x -> Flow Graph x
withBindings bindings = withEnvironment (\e -> M.union bindings e)

withEnvironment :: (M.Map Name TypeScheme -> M.Map Name TypeScheme) -> Flow Graph x -> Flow Graph x
withEnvironment m flow = do
  g <- getState
  withState (g {graphTypes = m (graphTypes g)}) flow

yield :: Term -> Type -> [TypeConstraint] -> Flow Graph (Inferred Term)
yield term typ constraints = do
  return (Inferred (TermTyped $ TypedTerm term typ) typ constraints)

yieldFunction :: Function -> Type -> [TypeConstraint] -> Flow Graph (Inferred Term)
yieldFunction fun = yield (TermFunction fun)

yieldElimination :: Elimination -> Type -> [TypeConstraint] -> Flow Graph (Inferred Term)
yieldElimination e = yield (TermFunction $ FunctionElimination e)