packages feed

hydra-0.12.0: src/main/haskell/Hydra/Sources/Kernel/Terms/Schemas.hs

{-# LANGUAGE OverloadedStrings #-}

module Hydra.Sources.Kernel.Terms.Schemas where

-- Standard imports for term-level kernel modules
import Hydra.Kernel
import Hydra.Sources.Libraries
import qualified Hydra.Dsl.Accessors     as Accessors
import qualified Hydra.Dsl.Ast           as Ast
import qualified Hydra.Dsl.Coders        as Coders
import qualified Hydra.Dsl.Compute       as Compute
import qualified Hydra.Dsl.Core          as Core
import qualified Hydra.Dsl.Grammar       as Grammar
import qualified Hydra.Dsl.Graph         as Graph
import qualified Hydra.Dsl.Json          as Json
import qualified Hydra.Dsl.Lib.Chars     as Chars
import qualified Hydra.Dsl.Lib.Equality  as Equality
import qualified Hydra.Dsl.Lib.Flows     as Flows
import qualified Hydra.Dsl.Lib.Lists     as Lists
import qualified Hydra.Dsl.Lib.Literals  as Literals
import qualified Hydra.Dsl.Lib.Logic     as Logic
import qualified Hydra.Dsl.Lib.Maps      as Maps
import qualified Hydra.Dsl.Lib.Math      as Math
import qualified Hydra.Dsl.Lib.Optionals as Optionals
import           Hydra.Dsl.Phantoms      as Phantoms
import qualified Hydra.Dsl.Lib.Sets      as Sets
import           Hydra.Dsl.Lib.Strings   as Strings
import qualified Hydra.Dsl.Mantle        as Mantle
import qualified Hydra.Dsl.Module        as Module
import qualified Hydra.Dsl.TTerms        as TTerms
import qualified Hydra.Dsl.TTypes        as TTypes
import qualified Hydra.Dsl.Terms         as Terms
import qualified Hydra.Dsl.Topology      as Topology
import qualified Hydra.Dsl.Types         as Types
import qualified Hydra.Dsl.Typing        as Typing
import           Hydra.Sources.Kernel.Types.All
import           Prelude hiding ((++))
import qualified Data.Int                as I
import qualified Data.List               as L
import qualified Data.Map                as M
import qualified Data.Set                as S
import qualified Data.Maybe              as Y

import qualified Hydra.Sources.Kernel.Terms.Constants as Constants
import qualified Hydra.Sources.Kernel.Terms.Decode.Core as DecodeCore
import qualified Hydra.Sources.Kernel.Terms.Encode.Core as EncodeCore
import qualified Hydra.Sources.Kernel.Terms.Lexical as Lexical
import qualified Hydra.Sources.Kernel.Terms.Monads as Monads
import qualified Hydra.Sources.Kernel.Terms.Names as Names
import qualified Hydra.Sources.Kernel.Terms.Rewriting as Rewriting
import qualified Hydra.Sources.Kernel.Terms.Show.Core as ShowCore
import qualified Hydra.Sources.Kernel.Terms.Sorting as Sorting
import qualified Hydra.Sources.Kernel.Terms.Variants as Variants


module_ :: Module
module_ = Module (Namespace "hydra.schemas") elements
    [DecodeCore.module_, EncodeCore.module_, Names.module_, Rewriting.module_,
      ShowCore.module_, Sorting.module_, Variants.module_]
    kernelTypesModules $
    Just ("Various functions for dereferencing and decoding schema types.")
  where
    elements = [
      el definitionDependencyNamespacesDef,
      el dependencyNamespacesDef,
      el dereferenceTypeDef,
      el elementAsTypedTermDef,
      el elementsWithDependenciesDef,
      el extendTypeContextForLambdaDef,
      el extendTypeContextForLetDef,
      el extendTypeContextForTypeLambdaDef,
      el fieldMapDef,
      el fieldTypeMapDef,
      el findFieldTypeDef,
      el fieldTypesDef,
      el fTypeToTypeSchemeDef,
      el fullyStripTypeDef,
      el graphAsTermDef,
      el graphAsTypesDef,
      el isEnumRowTypeDef,
      el isEnumTypeDef,
      el isSerializableDef,
      el moduleDependencyNamespacesDef,
      el namespacesForDefinitionsDef,
      el requireRecordTypeDef,
      el requireRowTypeDef,
      el requireTypeDef,
      el requireUnionTypeDef,
      el resolveTypeDef,
      el schemaGraphToTypingEnvironmentDef,
      el termAsGraphDef,
      el topologicalSortTypeDefinitionsDef,
      el typeDependenciesDef,
      el typeSchemeToFTypeDef,
      el typesToElementsDef]

define :: String -> TTerm a -> TBinding a
define = definitionInModule module_

definitionDependencyNamespacesDef :: TBinding ([Definition] -> S.Set Namespace)
definitionDependencyNamespacesDef = define "definitionDependencyNamespaces" $
  doc "Get dependency namespaces from definitions" $
  lambdas ["defs"] $ lets [
    "defNames">: lambda "def" $
      match _Definition Nothing [
        _Definition_type>>: lambda "typeDef" $
          ref Rewriting.typeDependencyNamesDef @@ true @@ Module.typeDefinitionType (var "typeDef"),
        _Definition_term>>: lambda "termDef" $
          ref Rewriting.termDependencyNamesDef @@ true @@ true @@ true @@ Module.termDefinitionTerm (var "termDef")]
      @@ var "def",
    "allNames">: Sets.unions $ Lists.map (var "defNames") (var "defs")]
    $ Sets.fromList $ Optionals.cat $ Lists.map (ref Names.namespaceOfDef) (Sets.toList $ var "allNames")

dependencyNamespacesDef :: TBinding (Bool -> Bool -> Bool -> Bool -> [Binding] -> Flow Graph (S.Set Namespace))
dependencyNamespacesDef = define "dependencyNamespaces" $
  doc "Find dependency namespaces in all of a set of terms" $
  lambdas ["binds", "withPrims", "withNoms", "withSchema", "els"] $ lets [
    "depNames">: lambda "el" $ lets [
      "term">: Core.bindingTerm $ var "el",
      "dataNames">: ref Rewriting.termDependencyNamesDef @@ var "binds" @@ var "withPrims" @@ var "withNoms" @@ var "term",
      "schemaNames">: Logic.ifElse (var "withSchema")
        (Optionals.maybe Sets.empty
          (lambda "ts" $ ref Rewriting.typeDependencyNamesDef @@ true @@ Core.typeSchemeType (var "ts"))
          (Core.bindingType $ var "el"))
        Sets.empty]
      $ Logic.ifElse (ref EncodeCore.isEncodedTypeDef @@ (ref Rewriting.deannotateTermDef @@ var "term"))
          (Flows.bind (ref DecodeCore.typeDef @@ var "term") $
            lambda "typ" $ Flows.pure $ Sets.unions $ list [
              var "dataNames", var "schemaNames",
              ref Rewriting.typeDependencyNamesDef @@ true @@ var "typ"])
          (Flows.pure $ Sets.unions $ list [var "dataNames", var "schemaNames"])]
    $ Flows.bind (Flows.mapList (var "depNames") (var "els")) $
      lambda "namesList" $ Flows.pure $ Sets.fromList $ Optionals.cat $ Lists.map (ref Names.namespaceOfDef) $
        Sets.toList $ Sets.delete (ref Constants.placeholderNameDef) $ Sets.unions $ var "namesList"

dereferenceTypeDef :: TBinding (Name -> Flow Graph (Maybe Type))
dereferenceTypeDef = define "dereferenceType" $
  doc "Dereference a type name to get the actual type" $
  "name" ~>
  "mel" <<~ ref Lexical.dereferenceElementDef @@ var "name" $
  optCases (var "mel")
    (Flows.pure nothing)
    ("el" ~> Flows.map (unaryFunction just) $ ref DecodeCore.typeDef @@ Core.bindingTerm (var "el"))

elementAsTypedTermDef :: TBinding (Binding -> Flow Graph TypedTerm)
elementAsTypedTermDef = define "elementAsTypedTerm" $
  doc "Convert an element to a typed term" $
  lambda "el" $
    Optionals.maybe (Flows.fail $ string "missing element type")
      (lambda "ts" $ Flows.pure $ Core.typedTerm (Core.bindingTerm $ var "el") (Core.typeSchemeType $ var "ts"))
      (Core.bindingType $ var "el")

elementsWithDependenciesDef :: TBinding ([Binding] -> Flow Graph [Binding])
elementsWithDependenciesDef = define "elementsWithDependencies" $
  doc "Get elements with their dependencies" $
  lambda "original" $ lets [
    "depNames">: lambda "el" $ Sets.toList $ ref Rewriting.termDependencyNamesDef @@ true @@ false @@ false @@ (Core.bindingTerm $ var "el"),
    "allDepNames">: Lists.nub $ Lists.concat2
      (Lists.map (unaryFunction Core.bindingName) (var "original"))
      (Lists.concat $ Lists.map (var "depNames") (var "original"))]
    $ Flows.mapList (ref Lexical.requireElementDef) (var "allDepNames")

extendTypeContextForLambdaDef :: TBinding (TypeContext -> Lambda -> TypeContext)
extendTypeContextForLambdaDef = define "extendTypeContextForLambda" $
  doc "Extend a type context by descending into a System F lambda body" $
  "tcontext" ~> "lam" ~>
  "var" <~ Core.lambdaParameter (var "lam") $
  "dom" <~ Optionals.fromJust (Core.lambdaDomain (var "lam")) $
  Typing.typeContextWithTypes
    (var "tcontext")
    (Maps.insert (var "var") (var "dom") (Typing.typeContextTypes (var "tcontext")))

extendTypeContextForLetDef :: TBinding (TypeContext -> Let -> TypeContext)
extendTypeContextForLetDef = define "extendTypeContextForLet" $
  doc "Extend a type context by descending into a let body" $
  "tcontext" ~> "letrec" ~>
  "bindings" <~ Core.letBindings (var "letrec") $
  Typing.typeContextWithTypes
    (var "tcontext")
    (Maps.union
      (Typing.typeContextTypes (var "tcontext"))
      (Maps.fromList $ Lists.map
        ("b" ~> pair
          (Core.bindingName $ var "b")
          (ref typeSchemeToFTypeDef @@ (Optionals.fromJust $ Core.bindingType $ var "b")))
        (var "bindings")))

extendTypeContextForTypeLambdaDef :: TBinding (TypeContext -> TypeLambda -> TypeContext)
extendTypeContextForTypeLambdaDef = define "extendTypeContextForTypeLambda" $
  doc "Extend a type context by descending into a System F type lambda body" $
  "tcontext" ~> "tlam" ~>
  "name" <~ Core.typeLambdaParameter (var "tlam") $
  Typing.typeContextWithVariables
    (var "tcontext")
    (Sets.insert (var "name") (Typing.typeContextVariables (var "tcontext")))

fieldMapDef :: TBinding ([Field] -> M.Map Name Term)
fieldMapDef = define "fieldMap" $
  "toPair" <~ ("f" ~> pair (Core.fieldName $ var "f") (Core.fieldTerm $ var "f")) $
  "fields" ~> Maps.fromList $ Lists.map (var "toPair") (var "fields")

fieldTypeMapDef :: TBinding ([FieldType] -> M.Map Name Type)
fieldTypeMapDef = define "fieldTypeMap" $
  "toPair" <~ ("f" ~> pair (Core.fieldTypeName $ var "f") (Core.fieldTypeType $ var "f")) $
  "fields" ~> Maps.fromList $ Lists.map (var "toPair") (var "fields")

fieldTypesDef :: TBinding (Type -> Flow Graph (M.Map Name Type))
fieldTypesDef = define "fieldTypes" $
  doc "Get field types from a record or union type" $
  lambda "t" $ lets [
    "toMap">: lambda "fields" $ Maps.fromList $ Lists.map
      (lambda "ft" $ pair (Core.fieldTypeName $ var "ft") (Core.fieldTypeType $ var "ft"))
      (var "fields")]
    $ match _Type (Just $ ref Monads.unexpectedDef @@ string "record or union type" @@ (ref ShowCore.typeDef @@ var "t")) [
      _Type_forall>>: lambda "ft" $ ref fieldTypesDef @@ Core.forallTypeBody (var "ft"),
      _Type_record>>: lambda "rt" $ Flows.pure $ var "toMap" @@ Core.rowTypeFields (var "rt"),
      _Type_union>>: lambda "rt" $ Flows.pure $ var "toMap" @@ Core.rowTypeFields (var "rt"),
      _Type_variable>>: lambda "name" $
        trace (Strings.cat2 (string "field types of ") (Core.unName $ var "name")) $
        Flows.bind (ref Lexical.requireElementDef @@ var "name") $
          lambda "el" $
            Flows.bind (ref DecodeCore.typeDef @@ Core.bindingTerm (var "el")) $
              ref fieldTypesDef]
    @@ (ref Rewriting.deannotateTypeDef @@ var "t")

findFieldTypeDef :: TBinding (Name -> [FieldType] -> Flow s Type)
findFieldTypeDef = define "findFieldType" $
  doc "Find a field type by name in a list of field types" $
  lambda "fname" $ lambda "fields" $ lets [
    "matchingFields">: Lists.filter
      (lambda "ft" $ Equality.equal (Core.unName $ Core.fieldTypeName $ var "ft") (Core.unName $ var "fname"))
      (var "fields")]
    $ Logic.ifElse (Lists.null $ var "matchingFields")
        (Flows.fail $ Strings.cat2 (string "No such field: ") (Core.unName $ var "fname"))
        (Logic.ifElse (Equality.equal (Lists.length $ var "matchingFields") (int32 1))
          (Flows.pure $ Core.fieldTypeType $ Lists.head $ var "matchingFields")
          (Flows.fail $ Strings.cat2 (string "Multiple fields named ") (Core.unName $ var "fname")))

fTypeToTypeSchemeDef :: TBinding (Type -> TypeScheme)
fTypeToTypeSchemeDef = define "fTypeToTypeScheme" $
  doc "Convert a forall type to a type scheme" $
  "gatherForall" <~ ("vars" ~> "typ" ~> cases _Type (ref Rewriting.deannotateTypeDef @@ var "typ")
     (Just $ Core.typeScheme (Lists.reverse $ var "vars") (var "typ")) [
     _Type_forall>>: "ft" ~> var "gatherForall" @@
       (Lists.cons (Core.forallTypeParameter $ var "ft") (var "vars")) @@
       (Core.forallTypeBody $ var "ft")]) $
  "typ" ~> var "gatherForall" @@ list [] @@ var "typ"

fullyStripTypeDef :: TBinding (Type -> Type)
fullyStripTypeDef = define "fullyStripType" $
  doc "Fully strip a type of forall quantifiers" $
  lambda "typ" $
    match _Type (Just $ var "typ") [
      _Type_forall>>: lambda "ft" $ ref fullyStripTypeDef @@ Core.forallTypeBody (var "ft")]
    @@ (ref Rewriting.deannotateTypeDef @@ var "typ")

graphAsTermDef :: TBinding (Graph -> Term)
graphAsTermDef = define "graphAsTerm" $
  doc "Convert a graph to a term, taking advantage of the built-in duality between graphs and terms" $
  "g" ~>
  "toBinding" <~ ("el" ~>
    "name" <~ Core.bindingName (var "el") $
    "term" <~ Core.bindingTerm (var "el") $
    "mts" <~ Core.bindingType (var "el") $
    Core.binding (var "name") (var "term") (var "mts")) $
  Core.termLet $ Core.let_
    (Lists.map (var "toBinding") (Maps.elems $ Graph.graphElements (var "g")))
    (Graph.graphBody (var "g"))

graphAsTypesDef :: TBinding (Graph -> Flow s (M.Map Name Type))
graphAsTypesDef = define "graphAsTypes" $
  doc "Decode a schema graph which encodes a set of named types" $
  "sg" ~>
  "els" <~ Maps.elems (Graph.graphElements (var "sg")) $
  "toPair" <~ ("el" ~>
    "typ" <<~ ref DecodeCore.typeDef @@ (Core.bindingTerm $ var "el") $
    produce $ pair (Core.bindingName $ var "el") (var "typ")) $
  "pairs" <<~ Flows.mapList (var "toPair") (var "els") $
  produce $ Maps.fromList $ var "pairs"

isEnumRowTypeDef :: TBinding (RowType -> Bool)
isEnumRowTypeDef = define "isEnumRowType" $
  doc "Check if a row type represents an enum (all fields are unit-typed)" $
  lambda "rt" $ Lists.foldl (binaryFunction Logic.and) true $
    Lists.map (lambda "f" $ ref EncodeCore.isUnitTypeDef @@ (Core.fieldTypeType $ var "f")) $
      Core.rowTypeFields $ var "rt"

isEnumTypeDef :: TBinding (Type -> Bool)
isEnumTypeDef = define "isEnumType" $
  doc "Check if a type is an enum type" $
  lambda "typ" $
    match _Type (Just false) [
      _Type_union>>: lambda "rt" $ ref isEnumRowTypeDef @@ var "rt"]
    @@ (ref Rewriting.deannotateTypeDef @@ var "typ")

isSerializableDef :: TBinding (Binding -> Flow Graph Bool)
isSerializableDef = define "isSerializable" $
  doc "Check if an element is serializable (no function types in dependencies)" $
  lambda "el" $ lets [
    "variants">: lambda "typ" $
      Lists.map (ref Variants.typeVariantDef) $ ref Rewriting.foldOverTypeDef @@ Coders.traversalOrderPre @@
        (lambda "m" $ lambda "t" $ Lists.cons (var "t") (var "m")) @@ list [] @@ var "typ"]
    $ Flows.map
      (lambda "deps" $ lets [
        "allVariants">: Sets.fromList $ Lists.concat $ Lists.map (var "variants") $ Maps.elems $ var "deps"]
        $ Logic.not $ Sets.member Mantle.typeVariantFunction $ var "allVariants")
      (ref typeDependenciesDef @@ false @@ (unaryFunction Equality.identity) @@ Core.bindingName (var "el"))

moduleDependencyNamespacesDef :: TBinding (Bool -> Bool -> Bool -> Bool -> Module -> Flow Graph (S.Set Namespace))
moduleDependencyNamespacesDef = define "moduleDependencyNamespaces" $
  doc "Find dependency namespaces in all elements of a module, excluding the module's own namespace" $
  lambdas ["binds", "withPrims", "withNoms", "withSchema", "mod"] $
    Flows.bind (ref dependencyNamespacesDef @@ var "binds" @@ var "withPrims" @@ var "withNoms" @@ var "withSchema" @@
      Module.moduleElements (var "mod")) $
      lambda "deps" $ Flows.pure $ Sets.delete (Module.moduleNamespace $ var "mod") (var "deps")

namespacesForDefinitionsDef :: TBinding ((Namespace -> a) -> Namespace -> [Definition] -> Namespaces a)
namespacesForDefinitionsDef = define "namespacesForDefinitions" $
  doc "Create namespaces mapping for definitions" $
  lambdas ["encodeNamespace", "focusNs", "defs"] $ lets [
    "nss">: Sets.delete (var "focusNs") $ ref definitionDependencyNamespacesDef @@ var "defs",
    "toPair">: lambda "ns" $ pair (var "ns") (var "encodeNamespace" @@ var "ns")]
    $ Module.namespaces (var "toPair" @@ var "focusNs") $ Maps.fromList $ Lists.map (var "toPair") $ Sets.toList $ var "nss"

requireRecordTypeDef :: TBinding (Name -> Flow Graph RowType)
requireRecordTypeDef = define "requireRecordType" $
  doc "Require a name to resolve to a record type" $
  ref requireRowTypeDef @@ string "record type" @@
    (lambda "t" $
      cases _Type (var "t") (Just nothing) [
        _Type_record>>: lambda "rt" $ just $ var "rt"])

requireRowTypeDef :: TBinding (String -> (Type -> Maybe RowType) -> Name -> Flow Graph RowType)
requireRowTypeDef = define "requireRowType" $
  doc "Require a name to resolve to a row type" $
  lambdas ["label", "getter", "name"] $ lets [
    "rawType">: lambda "t" $ cases _Type (var "t") (Just $ var "t") [
      _Type_annotated>>: lambda "at" $ var "rawType" @@ Core.annotatedTypeSubject (var "at"),
      _Type_forall>>: lambda "ft" $ var "rawType" @@ Core.forallTypeBody (var "ft")]]
    $ Flows.bind (ref requireTypeDef @@ var "name") $
      lambda "t" $
        Optionals.maybe
          (Flows.fail $ Strings.cat $ list [
            Core.unName $ var "name",
            string " does not resolve to a ",
            var "label",
            string " type: ",
            ref ShowCore.typeDef @@ var "t"])
          (unaryFunction Flows.pure)
          (var "getter" @@ (var "rawType" @@ var "t"))

requireTypeDef :: TBinding (Name -> Flow Graph Type)
requireTypeDef = define "requireType" $
  doc "Require a type by name" $
  lambda "name" $
  trace (Strings.cat2 (string "require type ") (Core.unName $ var "name")) $
  Flows.bind (ref Lexical.withSchemaContextDef @@ (ref Lexical.requireElementDef @@ var "name")) $
    lambda "el" $ ref DecodeCore.typeDef @@ Core.bindingTerm (var "el")

requireUnionTypeDef :: TBinding (Name -> Flow Graph RowType)
requireUnionTypeDef = define "requireUnionType" $
  doc "Require a name to resolve to a union type" $
  ref requireRowTypeDef @@ string "union" @@
    (lambda "t" $
      match _Type (Just nothing) [
        _Type_union>>: lambda "rt" $ just $ var "rt"]
      @@ var "t")

resolveTypeDef :: TBinding (Type -> Flow Graph (Maybe Type))
resolveTypeDef = define "resolveType" $
  doc "Resolve a type, dereferencing type variables" $
  lambda "typ" $
    match _Type (Just $ Flows.pure $ just $ var "typ") [
      _Type_variable>>: lambda "name" $
        ref Lexical.withSchemaContextDef @@
          (Flows.bind (ref Lexical.resolveTermDef @@ var "name") $
            lambda "mterm" $
              Optionals.maybe (Flows.pure nothing)
                (lambda "t" $ Flows.map (unaryFunction just) $ ref DecodeCore.typeDef @@ var "t")
                (var "mterm"))]
    @@ (ref Rewriting.deannotateTypeDef @@ var "typ")

schemaGraphToTypingEnvironmentDef :: TBinding (Graph -> Flow s (M.Map Name TypeScheme))
schemaGraphToTypingEnvironmentDef = define "schemaGraphToTypingEnvironment" $
  doc "Convert a schema graph to a typing environment" $
  "g" ~>
  "toTypeScheme" <~ ("vars" ~> "typ" ~> cases _Type (ref Rewriting.deannotateTypeDef @@ var "typ")
    (Just $ Core.typeScheme (Lists.reverse $ var "vars") (var "typ")) [
    _Type_forall>>: "ft" ~> var "toTypeScheme"
      @@ Lists.cons (Core.forallTypeParameter $ var "ft") (var "vars")
      @@ Core.forallTypeBody (var "ft")]) $
  "toPair" <~ ("el" ~> Flows.map
    ("mts" ~> Optionals.map ("ts" ~> pair (Core.bindingName $ var "el") (var "ts")) (var "mts"))
    (optCases (Core.bindingType $ var "el")
--      (Flows.pure nothing)
      ( "typ" <<~ ref DecodeCore.typeDef @@ (Core.bindingTerm $ var "el") $
        "ts" <~ ref fTypeToTypeSchemeDef @@ var "typ" $
        produce $ just $ var "ts")
      -- TODO: the following is a legacy solution; type schemes are encoded as types now, and do not need System F annotations
      ("ts" ~> Logic.ifElse
          (Equality.equal (var "ts") (Core.typeScheme (list []) (Core.typeVariable $ Core.nameLift _TypeScheme)))
          (Flows.map (unaryFunction just) $ ref DecodeCore.typeSchemeDef @@ Core.bindingTerm (var "el"))
          (Logic.ifElse
            (Equality.equal (var "ts") (Core.typeScheme (list []) (Core.typeVariable $ Core.nameLift _Type)))
            (Flows.map (lambda "decoded" $ just $ var "toTypeScheme" @@ list [] @@ var "decoded") $ ref DecodeCore.typeDef @@ Core.bindingTerm (var "el"))
            (cases _Term (ref Rewriting.deannotateTermDef @@ (Core.bindingTerm $ var "el")) (Just $ Flows.pure nothing) [
              _Term_record>>: lambda "r" $
                Logic.ifElse
                  (Equality.equal (Core.recordTypeName $ var "r") (Core.nameLift _TypeScheme))
                  (Flows.map
                    (unaryFunction just)
                    (ref DecodeCore.typeSchemeDef @@ Core.bindingTerm (var "el")))
                  (Flows.pure nothing),
              _Term_union>>: lambda "i" $
                Logic.ifElse (Equality.equal (Core.injectionTypeName $ var "i") (Core.nameLift _Type))
                  (Flows.map
                    (lambda "decoded" $ just $ var "toTypeScheme" @@ list [] @@ var "decoded")
                    (ref DecodeCore.typeDef @@ Core.bindingTerm (var "el")))
                  (Flows.pure nothing)]))))) $
  ref Monads.withStateDef @@ var "g" @@
    (Flows.bind (Flows.mapList (var "toPair") $ Maps.elems $ Graph.graphElements $ var "g") $
      lambda "mpairs" $ Flows.pure $ Maps.fromList $ Optionals.cat $ var "mpairs")

-- Note: this is lossy, as it throws away the term body
termAsGraphDef :: TBinding (Term -> (M.Map Name Term, Term))
termAsGraphDef = define "termAsGraph" $
  doc "Find the equivalent graph representation of a term" $
  "term" ~> cases _Term (ref Rewriting.deannotateTermDef @@ var "term")
    (Just Maps.empty) [
    _Term_let>>: "lt" ~>
      "bindings" <~ Core.letBindings (var "lt") $
      "fromBinding" <~ ("b" ~>
        "name" <~ Core.bindingName (var "b") $
        "term" <~ Core.bindingTerm (var "b") $
        "ts" <~ Core.bindingType (var "b") $
        pair (var "name") (Core.binding (var "name") (var "term") (var "ts"))) $
      Maps.fromList $ Lists.map (var "fromBinding") (var "bindings")]

topologicalSortTypeDefinitionsDef :: TBinding ([TypeDefinition] -> [[TypeDefinition]])
topologicalSortTypeDefinitionsDef = define "topologicalSortTypeDefinitions" $
  doc "Topologically sort type definitions by dependencies" $
  lambda "defs" $ lets [
    "toPair">: lambda "def" $ pair
      (Module.typeDefinitionName $ var "def")
      (Sets.toList $ ref Rewriting.typeDependencyNamesDef @@ false @@ Module.typeDefinitionType (var "def")),
    "nameToDef">: Maps.fromList $ Lists.map
      (lambda "d" $ pair (Module.typeDefinitionName $ var "d") (var "d"))
      (var "defs"),
    "sorted">: ref Sorting.topologicalSortComponentsDef @@ Lists.map (var "toPair") (var "defs")]
    $ Lists.map (lambda "names" $ Optionals.cat $ Lists.map (lambda "n" $ Maps.lookup (var "n") (var "nameToDef")) (var "names")) $
      var "sorted"

typeDependenciesDef :: TBinding (Bool -> (Type -> Type) -> Name -> Flow Graph (M.Map Name Type))
typeDependenciesDef = define "typeDependencies" $
  doc "Get all type dependencies for a given type name" $
  lambdas ["withSchema", "transform", "name"] $ lets [
  "requireType">: lambda "name" $
    trace (Strings.cat2 (string "type dependencies of ") (Core.unName $ var "name")) $
    Flows.bind (ref Lexical.requireElementDef @@ var "name") $
      lambda "el" $ ref DecodeCore.typeDef @@ Core.bindingTerm (var "el"),
  "toPair">: lambda "name" $
    Flows.bind (var "requireType" @@ var "name") $
      lambda "typ" $ Flows.pure $ pair (var "name") (var "transform" @@ var "typ"),
  "deps">: lambda "seeds" $ lambda "names" $
    Logic.ifElse (Sets.null $ var "seeds")
      (Flows.pure $ var "names")
      (Flows.bind (Flows.mapList (var "toPair") $ Sets.toList $ var "seeds") $
        lambda "pairs" $ lets [
          "newNames">: Maps.union (var "names") $ Maps.fromList $ var "pairs",
          "refs">: Lists.foldl (binaryFunction Sets.union) Sets.empty $ Lists.map
            (lambda "pair" $ ref Rewriting.typeDependencyNamesDef @@ var "withSchema" @@ second (var "pair"))
            (var "pairs"),
          "visited">: Sets.fromList $ Maps.keys $ var "names",
          "newSeeds">: Sets.difference (var "refs") (var "visited")]
          $ var "deps" @@ var "newSeeds" @@ var "newNames")] $
    var "deps" @@ Sets.singleton (var "name") @@ Maps.empty

typeSchemeToFTypeDef :: TBinding (TypeScheme -> Type)
typeSchemeToFTypeDef = define "typeSchemeToFType" $
  doc "Convert a type scheme to a forall type" $
  "ts" ~>
  "vars" <~ Core.typeSchemeVariables (var "ts") $
  "body" <~ Core.typeSchemeType (var "ts") $
  Lists.foldl
    ("t" ~> "v" ~> Core.typeForall $ Core.forallType (var "v") (var "t"))
    (var "body")
    -- Put the variables in the same order in which they are introduced by the type scheme.
    (Lists.reverse $ var "vars")

typesToElementsDef :: TBinding (M.Map Name Type -> M.Map Name Binding)
typesToElementsDef = define "typesToElements" $
  doc "Encode a map of named types to a map of elements" $
  "typeMap" ~>
  "toElement" <~ ("pair" ~>
    "name" <~ first (var "pair") $
    pair
      (var "name")
      (Core.binding
        (var "name")
        (ref EncodeCore.typeDef @@ (second $ var "pair"))
        nothing)) $
  Maps.fromList $ Lists.map (var "toElement") $ Maps.toList $ var "typeMap"