hydra-0.5.0: src/main/haskell/Hydra/Inference.hs
-- | Entry point for Hydra type inference, which is a variation on on Hindley-Milner
module Hydra.Inference (
annotateTypedTerms,
inferGraphTypes,
inferType,
inferTypeScheme,
inferTypeAndConstraints,
Constraint,
) where
import Hydra.Compute
import Hydra.Core
import Hydra.CoreEncoding
import Hydra.Graph
import Hydra.Lexical
import Hydra.Mantle
import Hydra.Annotations
import Hydra.Rewriting
import Hydra.Substitution
import Hydra.Unification
import Hydra.Rules
import Hydra.Tier1
import Hydra.Tier2
import Hydra.Tools.Sorting
import qualified Hydra.Dsl.Terms as Terms
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
annotateElements :: Graph -> [Element] -> Flow Graph [Element]
annotateElements g sortedEls = withInferenceContext $ do
iels' <- annotate sortedEls ([])
let iels = fst <$> iels'
let constraints = snd <$> iels'
-- Note: inference occurs over the entire graph at once,
-- but unification and substitution occur within elements in isolation
subst <- withGraphContext $ withSchemaContext $ CM.mapM solveConstraints constraints
return $ L.zipWith rewriteElement subst iels
where
-- Note: the following defaults to user-provided type annotations where provided.
-- In the future, we should trust unification to perform this defaulting, and not override the inferred type.
rewriteElement subst el = el { elementData = setTermType (Just typ) term1 }
where
term0 = elementData el
term1 = rewriteDataType (substituteInType subst) term0
typ = Y.fromMaybe (termType term1) $ getTermType term0
annotate :: [Element] -> [(Element, [Constraint])] -> Flow InferenceContext [(Element, [Constraint])]
annotate original annotated = case original of
[] -> pure $ L.reverse annotated
(el:r) -> do
(iel, c1) <- inferElementType el
withBinding (elementName el) (termTypeScheme $ elementData iel) $ annotate r ((iel, c1):annotated)
annotateTypedTerms :: Term -> Flow Graph Term
annotateTypedTerms term0 = do
(term1, _) <- inferTypeAndConstraints term0
return term1
inferElementType :: Element -> Flow InferenceContext (Element, [Constraint])
inferElementType el = withTrace ("infer type of " ++ unName (elementName el)) $ do
(iterm, c) <- infer $ elementData el
return (el {elementData = iterm}, c)
inferGraphTypes :: Flow Graph Graph
inferGraphTypes = getState >>= annotateGraph
where
annotateGraph g = withTrace ("infer graph types") $ do
sorted <- sortGraphElements g
els <- sortGraphElements g >>= annotateElements g
return g {graphElements = M.fromList (toPair <$> els)}
where
toPair el = (elementName el, el)
-- TODO: deprecated
inferType :: Term -> Flow Graph Type
inferType term = typeSchemeType <$> inferTypeScheme term
-- TODO: deprecated
-- | Solve for the top-level type of an expression in a given environment
inferTypeAndConstraints :: Term -> Flow Graph (Term, TypeScheme)
inferTypeAndConstraints term = withTrace ("infer type") $ withInferenceContext $ do
(iterm, constraints) <- infer term
subst <- withGraphContext $ withSchemaContext $ solveConstraints constraints
let term2 = rewriteDataType (substituteInType subst) iterm
-- let typ = Y.fromMaybe (termType term2) $ getTermType term
-- return (setTermType (Just typ) term2, closeOver $ termType term2)
return (term2, closeOver $ termType term2)
where
-- | Canonicalize and return the polymorphic top-level type.
closeOver = normalizeScheme . generalize M.empty . reduceType
-- TODO: deprecated
inferTypeScheme :: Term -> Flow Graph TypeScheme
inferTypeScheme term = snd <$> inferTypeAndConstraints term
rewriteDataType :: (Type -> Type) -> Term -> Term
rewriteDataType f = rewriteTerm ff id
where
ff recurse term = case recurse term of
TermTyped (TypedTerm term1 type1) -> TermTyped $ TypedTerm term1 (f type1)
t -> t
sortGraphElements :: Graph -> Flow Graph [Element]
sortGraphElements g = do
let annotated = S.fromList $ Y.catMaybes (ifAnnotated <$> M.elems els)
adjList <- CM.mapM (toAdj annotated) $ M.elems els
case topologicalSort adjList of
Left comps -> fail $ "cyclical dependency not resolved through annotations: " ++ L.intercalate ", " (unName <$> L.head comps)
Right names -> return $ Y.catMaybes ((\n -> M.lookup n els) <$> names)
where
els = graphElements g
ifAnnotated el = case (getTermType $ elementData el) of
Nothing -> Nothing
Just _ -> Just $ elementName el
toAdj annotated el = do
let deps = L.filter isNotAnnotated $ L.filter isElName $ S.toList $ freeVariablesInTerm $ elementData el
return (elementName el, deps)
where
-- Ignore free variables which are not valid element references
isElName name = M.member name els
-- No need for an inference dependency on an element which is already annotated with a type
isNotAnnotated name = not $ S.member name annotated
withInferenceContext flow = do
g <- getState
let env = initialEnv g
withState (InferenceContext g env) flow
where
initialEnv g = M.fromList $ Y.catMaybes (toPair <$> (M.elems $ graphElements g))
where
toPair el = (\t -> (elementName el, monotype t)) <$> (getTermType $ elementData el)