packages feed

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

-- | Wrapper for @wisnesky's Algorithm W implementation which makes it into an alternative inferencer for Hydra

module Hydra.Inference.AlgorithmWBridge where

import Hydra.Inference.AlgorithmW

import qualified Hydra.Core as Core
import qualified Hydra.Graph as Graph
import qualified Hydra.Dsl.Literals as Literals
import qualified Hydra.Dsl.LiteralTypes as LiteralTypes
import qualified Hydra.Dsl.Terms as Terms
import qualified Hydra.Dsl.Types as Types
import Hydra.Sources.Libraries
import Hydra.Basics
import Hydra.Strip
import Hydra.Tier1
import Hydra.Coders
import Hydra.Inference.Substitution
import Hydra.Rewriting

import qualified Data.List as L
import qualified Data.Map as M
import qualified Control.Monad as CM

import Control.Monad.Except
import Control.Monad.State


-- A minimal Hydra graph container for use in these translation functions
data HydraContext = HydraContext (M.Map Core.Name Graph.Primitive)

----------------------------------------

-- | Find all of the bound variables in the type annotations within a System F term.
--   This function considers the types in "typed terms" (term:type), domain types on lambdas (\v:type.term),
--   and also type abstractions (/\v.term) to provide bound type variables.
boundTypeVariablesInSystemFTerm :: Core.Term -> [Core.Name]
boundTypeVariablesInSystemFTerm = L.nub . foldOverTerm TraversalOrderPost addTypeVars []
  where
    addTypeVars vars term = typeVarsIn term ++ vars
    typeVarsIn term = case term of
      Core.TermFunction (Core.FunctionLambda (Core.Lambda _ (Just typ) _)) -> boundVariablesInTypeOrdered typ
      Core.TermTypeAbstraction (Core.TypeAbstraction v _) -> [v]
      Core.TermTyped (Core.TypedTerm term typ) -> boundVariablesInTypeOrdered typ
      _ -> []

boundTypeVariablesInTermOrdered :: Core.Term -> [Core.Name]
boundTypeVariablesInTermOrdered = L.nub . foldOverTerm TraversalOrderPre fld []
  where
    fld vars term = case term of
      Core.TermTyped (Core.TypedTerm _ typ) -> variablesInTypeOrdered False typ ++ vars
      _ -> vars

-- | Finds all of the universal type variables in a type expression, in the order in which they appear.
-- Note: this function assumes that there are no shadowed type variables, as in (forall a. forall a. a)
-- TODO: redundant with variablesInTypeOrdered
boundVariablesInTypeOrdered :: Core.Type -> [Core.Name]
boundVariablesInTypeOrdered typ = case typ of
  Core.TypeLambda (Core.LambdaType var body) -> var:(boundVariablesInTypeOrdered body)
  t -> L.concat (boundVariablesInTypeOrdered <$> subtypes t)

-- | Replace arbitrary bound type variables like v, a, v_12 with the systematic type variables t0, t1, t2, ...
--   following a canonical ordering in the term.
--   This function assumes that the bound variables do not also appear free in the type expressions of the term,
--   which in Hydra is made less likely by using the unusual naming convention tv_0, tv_1, etc. for temporary variables.
normalizeBoundTypeVariablesInSystemFTerm :: Core.Term -> Core.Term
normalizeBoundTypeVariablesInSystemFTerm term = replaceTypeVariablesInSystemFTerm subst term
  where
    actualVars = boundTypeVariablesInSystemFTerm term
    subst = M.fromList $ L.zip actualVars normalVariables

replaceTypeVariables :: M.Map Core.Name Core.Name -> Core.Type -> Core.Type
replaceTypeVariables subst = rewriteType $ \recurse t -> case recurse t of
    Core.TypeVariable v -> Core.TypeVariable $ replace v
    Core.TypeLambda (Core.LambdaType v body) -> Core.TypeLambda $ Core.LambdaType (replace v) body
    t1 -> t1
  where
    replace v = M.findWithDefault v v subst

-- Note: this will replace all occurrences, regardless of boundness or shadowing
replaceTypeVariablesInSystemFTerm :: M.Map Core.Name Core.Name -> Core.Term -> Core.Term
replaceTypeVariablesInSystemFTerm subst = rewriteTerm $ \recurse term ->
  case recurse term of
    Core.TermFunction (Core.FunctionLambda (Core.Lambda v (Just mt) body)) ->
        Core.TermFunction $ Core.FunctionLambda $ Core.Lambda v (Just mt2) body
      where
        mt2 = replaceTypeVariables subst mt
    Core.TermTypeAbstraction (Core.TypeAbstraction v body) -> Core.TermTypeAbstraction $ Core.TypeAbstraction v2 body
      where
        v2 = M.findWithDefault v v subst
    Core.TermTyped (Core.TypedTerm term typ) -> Core.TermTyped $ Core.TypedTerm term (replaceTypeVariables subst typ)
    t -> t

-- | Find the variables (both bound and free) in a type expression, following a preorder traversal of the expression.
variablesInTypeOrdered :: Bool -> Core.Type -> [Core.Name]
variablesInTypeOrdered onlyBound = L.nub . vars -- Note: we rely on the fact that 'nub' keeps the first occurrence
  where
    vars t = case t of
      Core.TypeLambda (Core.LambdaType v body) -> v:(vars body)
      Core.TypeVariable v -> if onlyBound then [] else [v]
      _ -> L.concat (vars <$> subtypes t)

----------------------------------------

-- Note: no support for @wisnesky's Prim constructors other than PrimStr, PrimNat, Cons, and Nil
hydraTermToStlc :: HydraContext -> Core.Term -> Either String Expr
hydraTermToStlc context term = case term of
    Core.TermApplication (Core.Application t1 t2) -> App <$> toStlc t1 <*> toStlc t2
    Core.TermFunction f -> case f of
      Core.FunctionLambda (Core.Lambda (Core.Name v) _ body) -> Abs <$> pure v <*> toStlc body
      Core.FunctionPrimitive name -> do
        prim <- case M.lookup name prims of
          Nothing -> Left $ "no such primitive: " ++ Core.unName name
          Just p -> Right p
        ts <- hydraTypeSchemeToStlc $ Graph.primitiveType prim
        return $ Const $ TypedPrim $ TypedPrimitive name ts
    Core.TermLet (Core.Let bindings env) -> Letrec <$> CM.mapM bindingToStlc bindings <*> toStlc env
      where
        bindingToStlc (Core.LetBinding (Core.Name v) term _) = do
          s <- toStlc term
          return (v, s)
    Core.TermList els -> do
      sels <- CM.mapM toStlc els
      return $ foldr (\el acc -> App (App (Const Cons) el) acc) (Const Nil) sels
    Core.TermLiteral lit -> pure $ Const $ Lit lit
    Core.TermProduct els -> Tuple <$> (CM.mapM toStlc els)
    Core.TermVariable (Core.Name v) -> pure $ Var v
    _ -> Left $ "Unsupported term: " ++ show term
  where
    HydraContext prims = context
    toStlc = hydraTermToStlc context
    pair a b = App (App (Const Pair) a) b

hydraTypeSchemeToStlc :: Core.TypeScheme -> Either String TypSch
hydraTypeSchemeToStlc (Core.TypeScheme vars body) = do
    sbody <- toStlc body
    return $ Forall (Core.unName <$> vars) sbody
  where
    toStlc typ = case stripType typ of
      Core.TypeFunction (Core.FunctionType dom cod) -> TyFn <$> toStlc dom <*> toStlc cod
      Core.TypeList et -> TyList <$> toStlc et
      Core.TypeLiteral lt -> pure $ TyLit lt
--      TypeMap MapType |
--      TypeOptional Type |
      Core.TypeProduct types -> TyTuple <$> (CM.mapM toStlc types)
--      TypeRecord RowType |
--      TypeSet Type |
--      TypeStream Type |
      Core.TypeSum types -> if L.length types == 0
        then pure TyVoid
        else if L.length types == 1
          then Left $ "unary sums are not yet supported"
          else do
            stypes <- CM.mapM toStlc types
            let rev = L.reverse stypes
            return $ L.foldl (\a e -> TySum e a) (TySum (rev !! 1) (rev !! 0)) $ L.drop 2 rev
--      TypeUnion RowType |
      Core.TypeVariable name -> pure $ TyVar $ Core.unName name
--      TypeWrap (Nominal Type)
      _ -> Left $ "unsupported type: " ++ show typ


-- hydraTypeToTypeScheme :: Core.Type -> Either String TypSch
-- hydraTypeToTypeScheme typ = do
--     let (boundVars, baseType) = splitBoundVars [] typ
--     ty <- toStlc baseType
--     return $ Forall (Core.unName <$> boundVars) ty
--   where
--     splitBoundVars vars typ = case stripType typ of
--       Core.TypeLambda (Core.LambdaType v body) -> (v:vars', typ')
--         where
--           (vars', typ') = splitBoundVars vars body
--       _ -> (vars, typ)

systemFExprToHydra :: FExpr -> Either String Core.Term
systemFExprToHydra expr = case expr of
  FConst prim -> case prim of
    Lit lit -> pure $ Core.TermLiteral lit
    TypedPrim (TypedPrimitive name _) -> pure $ Core.TermFunction $ Core.FunctionPrimitive name
    Nil -> pure $ Core.TermList []
    _ -> Left $ "Unsupported primitive: " ++ show prim
    -- Note: other prims are unsupported
  FVar v -> pure $ Core.TermVariable $ Core.Name v
  FApp e1 e2 -> case e1 of
    FApp (FTyApp (FConst Cons) _) hd -> do
        els <- CM.mapM systemFExprToHydra (hd:(gather e2))
        return $ Core.TermList els -- TODO: include inferred type
      where
        gather e = case e of
          FTyApp (FConst Nil) _ -> []
          FApp (FApp (FTyApp (FConst Cons) _) hd) tl -> hd:(gather tl)
    FTyApp (FConst Pair) _ -> do
--        els <- CM.mapM systemFExprToHydra (gather expr)
        els <- pure []
        return $ Core.TermProduct els -- TODO: include inferred type
      where
        gather e = case e of
          FApp (FApp (FTyApp (FConst Pair) _) el) arg -> el:(gather arg)
          _ -> [e]
    _ -> Core.TermApplication <$> (Core.Application <$> systemFExprToHydra e1 <*> systemFExprToHydra e2)
  FAbs v dom e -> do
    term <- systemFExprToHydra e
    hdom <- Core.typeSchemeType <$> systemFTypeToHydra dom
    return $ Core.TermFunction $ Core.FunctionLambda (Core.Lambda (Core.Name v) (Just hdom) term)
  FTyAbs params body -> do
    hbody <- systemFExprToHydra body
    return $ L.foldl (\t v -> Core.TermTypeAbstraction $ Core.TypeAbstraction (Core.Name v) t) hbody $ L.reverse params
  FTyApp fun args -> do
    hfun <- systemFExprToHydra fun
    hargs <- CM.mapM (\t -> Core.typeSchemeType <$> systemFTypeToHydra t) args
    return $ L.foldl (\t a -> Core.TermTypeApplication $ Core.TypedTerm t a) hfun $ L.reverse hargs
  FLetrec bindings env -> Core.TermLet <$>
      (Core.Let <$> CM.mapM bindingToHydra bindings <*> systemFExprToHydra env)
    where
      bindingToHydra (v, ty, term) = do
        hterm <- systemFExprToHydra term
        hts <- systemFTypeToHydra ty
        return $ Core.LetBinding (Core.Name v) hterm $ Just hts
  FTuple els -> Core.TermProduct <$> (CM.mapM systemFExprToHydra els)
  FInj i types e -> Core.TermSum <$> (Core.Sum i (L.length types) <$> systemFExprToHydra e)

systemFTypeToHydra :: FTy -> Either String Core.TypeScheme
systemFTypeToHydra ty = case ty of
    FForall vars body -> Core.TypeScheme (Core.Name <$> vars) <$> toHydra body
    _ -> Core.TypeScheme [] <$> toHydra ty
  where
    toHydra ty = case ty of
      FTyVar v -> pure $ Core.TypeVariable $ Core.Name v
      FTyLit lt -> pure $ Core.TypeLiteral lt
      FTyList lt -> Core.TypeList <$> toHydra lt
      FTyFn dom cod -> Core.TypeFunction <$> (Core.FunctionType <$> toHydra dom <*> toHydra cod)
      FTyProd t1 t2 -> Core.TypeProduct <$> CM.mapM toHydra (t1:(componentsTypesOf t2))
        where
          componentsTypesOf t = case t of
            FTyProd t1 t2 -> t1:(componentsTypesOf t2)
            _ -> [t]
      FTySum t1 t2 -> Core.TypeSum <$> CM.mapM toHydra (t1:(componentsTypesOf t2))
        where
          componentsTypesOf t = case t of
            FTySum t1 t2 -> t1:(componentsTypesOf t2)
            _ -> [t]
      FTyUnit -> pure $ Core.TypeProduct []
      FTyVoid -> pure $ Core.TypeSum []
      FTyTuple tys -> Core.TypeProduct <$> (CM.mapM toHydra tys)
      FTyVariant tys -> Core.TypeSum <$> (CM.mapM toHydra tys)

inferWithAlgorithmW :: HydraContext -> Core.Term -> IO Core.Term
inferWithAlgorithmW context term = do
    stlc <- case hydraTermToStlc context (wrap term) of
       Left err -> fail err
       Right t -> return t
    (fexpr, _) <- inferExpr stlc
    case systemFExprToHydra fexpr of
      Left err -> fail err
      Right t -> normalizeBoundTypeVariablesInSystemFTerm <$> unwrap t
  where
    sFieldName = Core.Name "tempVar"
    wrap term = Core.TermLet $ Core.Let ([Core.LetBinding sFieldName term Nothing]) $
      Core.TermLiteral $ Core.LiteralString "tempEnvironment"
    unwrap term = case term of
      Core.TermLet (Core.Let bindings _) -> case bindings of
        [(Core.LetBinding fname t _)] -> if fname == sFieldName
          then pure t
          else fail "expected let binding matching input"
        _ -> fail "expected let bindings"

inferExpr :: Expr -> IO (FExpr, FTy)
inferExpr t = case (fst $ runState (runExceptT (w 0 [] [] t)) ([],0)) of
  Left e -> fail $ "inference error: " ++ e
  Right (_, (ty, f)) -> case (typeOf [] [] [] f) of
    Left err -> fail $ "type error: " ++ err
    Right tt -> if tt == mTyToFTy ty
      then return (f, tt)
      else fail "no match"