260 likes | 411 Views
Derivando un algoritmo de inferencia de tipos. El sistema de inferencia polim ó rfico no es syntax-directed. Las reglas (inst) y (gen) pueden ser aplicadas en cualquier paso de la derivación. Sin embargo, es posible restringir el uso de:
E N D
Derivando un algoritmo de inferencia de tipos • El sistema de inferencia polimórfico no es syntax-directed. Las reglas (inst) y (gen) pueden ser aplicadas en cualquier paso de la derivación. • Sin embargo, es posible restringir el uso de: • (inst) inmediatamente después de las reglas (var) y (con) • (gen) inmediatamente antes de (let) para obtener los esquemas más generales para las variables definidas. • A continuación presentaremos un sistema modificado que fuerza las restricciones arriba discutidas, y que no incluye las reglas (inst) y (gen). • Def. Esquema más general • Gen(,T) = a1 ...an. T donde {a1 ...an}= FV(T) - FV()
(var) |- x : T si x : S está en y S T (con) |- c : T si c : S está en y S T |- f : T -> T’ |- e : T (ap) --------------------- |- f e : T’ |- a : T |- b : T’ (prod) ----------------- |- (a,b) : T x T’ |- b : Bool |- e : T |- d : T (if) ----------------------------- |- if b then e else d : T |- e’ : T’ ,x:S|- e : T (let) --------------------- donde S = Gen( ,T’) |- let x = e’ in e : T , x:T’|- e : T (abs) --------------- |- \ x -> e : T’ -> T
Representación de expresiones de tipos Para construír el algoritmo necesitaremos representar expresiones de tipos como objetos de un tipo Haskell: type TVname = [Int] data Texp = Tvar TVname | Tcons String [Texp] deriving(Eq,Read,Show) -- some types arrow t1 t2 = Tcons "arrow" [t1,t2] int = Tcons "int" [] bool = Tcons ”bool" [] cross t1 t2 = Tcons "cross" [t1,t2] list t = Tcons "list" [t]
La mónada [] instance Monad [] where return a = [a] l >= f = concat (map f l) bar :: Eq a => [a]->[a]->[a] bar l b = [ x l | x notElem b ] -- the type variables in a type tvars_in (Tvar x) = return x tvars_in (Tcons name args) = args >= tvars_in
Resolviendo ecuaciones Consideremos el chequeo de una expresión (Ap e1 e2), donde hemos derivado el tipo t1 para e1 y el tipo t2 para e2. Entonces tendríamos que resolver la siguiente ecuación: t1 = t2 -> (Tvar n) donde n es un nombre de variable de tipo que no ha sido previamente usado. Como ya hemos visto en el chequeo de una expresión se generan restricciones, las que pueden ser entendidas como ecuaciones entre (esquemas) de tipos. Soluciones a estas ecuaciones pueden ser entendidas (e implementadas) como sustituciones de variables (de tipos) por expresiones de tipos. Entonces: -- substitutions type Subst = TVname -> Texp
Sustituciones Dada una sustitución phi y una expresión de tipo te, definimos a (sub_type phi te) como la expresión obtenida a partir de aplicar la sustitución phi a todas las variables de tipo en te: sub_type :: Subst -> Texp -> Texp sub_type phi (Tvar x) = phi x sub_type phi (Tcons name args) = Tcons name (map (sub_type phi) args) Dos sustituciones pueden ser compuestas para definir otra sustitución: subst_comp :: Subst -> Subst -> Subst subst_comp phi psi x = sub_type phi (psi x) Propiedad: sub_type (subst_comp phi psi) = (sub_type phi) . (sub_type psi)
Sustituciones (2) La sustitución identidad id_subst :: Subst id_subst x = Tvar x Una sustitución delta es aquella que sólo afecta a una variable delta :: TVname -> Texp -> Subst delta x t y = if y == x then t else Tvar y En general una sustitución puede asociarle a una variable un valor que a su vez tambien contiene variables. Si esas variables a su vez forman parte del dominio de la sustitución, entonces la misma no esta “completamente resuelta”. Un proceso iterativo podría ser necesario (problema de terminación si hay circularidad).
Sustituciones (3) En general, estaremos interesados en obtener sustituciones normalizadas. La siguiente definición captura esta noción Def. Una sustitución phies idempotente si se cumple que (sub_type phi) . (sub_type phi) = sub_type phi Def. Una expresión de tipo t es un punto fijo de una sustitución phi si sub_type phi t = t En particular, si (Tvar x) es un punto fijo de phi, diremos que x no es afectada por phi. Notar que si phi es idempotente, y phi afecta a tvn, entonces sub_type phi (Var tvn) es un punto fijo de phi
Unificación Ahora mostraremos cómo construír una sustitución que resuelva un conjunto dado de ecuaciones de tipos, usando unificación. Un sistema de ecuaciones puede ser representado como una lista de pares de expresiones de tipos. Para resolver las ecuaciones, debemos hallar una sustitución phi que unifique las partes izquierdas y derechas de todas las ecuaciones en el sistema, donde decimos que phi unifica al par (t1, t2) si se cumple sub_type phi t1 = sub_type phi t2 Def. Una sustitución phi es no menos general que una sustitución psi si existe rho tal que psi = rho `subst_comp` phi
Unificación (2) El problema de unificación es el de hallar un unificador maximalmente general e idempotente de un conjunto de pares de expresiones [Robinson 65]. Será conveniente para la construcción del algoritmo considerar el problema de extender una sustitución que resuelve un conjunto de ecuaciones a una que resuelve una extension de ese conjunto. Formularemos el problema de la siguiente forma: Dado un par (t1,t2) de expresiones de tipos, y una sustitución idempotente phi, nuestro algoritmo debería retornar un error si no existe una extensión de phi que unifica (t1,t2), y si no retornar (Ok psi) donde psi es un unificador idempotente de (t1,t2) que extiende a phi.
Unificación (3) La ecuación más simple a resolver es una de la forma Tvar tvn = t Para manejar estos casos haremos uso de la siguiente función extend :: Subst -> TVname -> Texp -> E Subst extend phi x t = if t == Tvar x then return phi else if elem x (tvars_in t) then errorE “occur check” else return (subst_comp (delta x t) phi) Una expresión (extend phi tvn t) será evaluada sólo cuando: i) phi es una sustitución idempotente ii) t es un punto fijo de phi iii) tvn no es afectada por phi (tvn no tiene un valor bajo phi)
Unificación (4) unify :: Subst -> (Texp,Texp) -> E Subst unify phi (Tvar x,t) = if phix == Tvar x then extend phi x t else unify phi (phix,phit) where phix = phi x phit = sub_type phi t unify phi (Tcons n args,Tvar x) = unify phi (Tvar x,Tcons n args) unify phi (Tcons n1 args1 ,Tcons n2 args2) = if n1 == n2 then unifyl (return phi) (zip args1 args2) else errorE “Different type constructors” unifyl :: E Subst -> [(Texp,Texp)] -> E Subst unifyl = foldr (\eq -> \ms -> ms >= (\s -> unify s eq))
Esquemas de tipos y genericidad Representaremos esquemas de tipos como objetos del siguiente tipo Haskell data TypeScheme = Scheme [TVname] Texp Una variable de tipo que ocurre en un esquema (Scheme scvs t) es genérica si su nombre es un elemento de la lista scvs, en caso contrario será una variable no-genérica nonGenericOfScheme :: TypeScheme -> [TVname] nonGenericOfScheme (Scheme l t) = bar (tvars_in t) l Aplicando una sustitución a un esquema de tipo sin afectar variables genéricas sub_scheme :: Subst -> TypeScheme -> TypeScheme sub_scheme phi (Scheme l t) = Scheme l (sub_type (exclude phi l) t) where exclude phi l x = if (elem x l) then (Tvar x) else (phi x)
Listas de asociación Cómo reflejar el hecho de que a cada variable libre en una expresión le asociaremos un esquema de tipo? Requerimientos sobre le estructura de datos: i) debe proveer un mapeo de variables libres a esquemas ii) tendríamos que ser capaces de determinar cuál es el recorrido de ese mapping Consideremos el chequeo de (let x = e in e´) 1) derivamos un tipo T para e en un contexto x1 : S1 ... xn : Sn, lo que equivale a construir una sustitución phi para las ecuaciones implicadas por la estructura de e tal que e : T en el contexto x1 : S1´ ... xn : Sn´, donde Si´ es la imagen de Sibajophi. 2) Formamos el esquema S que asociaremos a x en el contexto, tal que las variables genéricas de S son aquellas de T excepto las no-genéricas que ocurran en S1´ ... Sn´.
Listas de asociación (2) Entonces cualquiera sea la estructura elegida para representar contextos nos deberá permitir acceso al conjunto de variables no-genéricas de su recorrido. type AssocList a b = [(a,b)] dom :: AssocList a b -> [a] val :: Eq a => AssocList a b -> a -> b install :: AssocList a b -> a -> b -> AssocList a b -- the range of an association list is the list of -- values (as obtained with val) rng :: Eq a => AssocList a b -> [b] rng al = map (val al) (dom al)
Contextos type Context = AssocList Vname TypeScheme nonGenericOfCtx :: Context -> [TVname] nonGenericOfCtx gamma = (rng gamma) >= nonGenericOfScheme sub_ctx :: Subst -> Context -> Context sub_ctx phi gamma = gamma >= (\(x,st) -> [(x,(sub_scheme phi st))]) Variables Frescas type NameSupply = TVname name_sequence :: NameSupply -> [TVname]
Inferencia de tipos Finalmente, ahora estamos en condiciones de definir el algoritmo de inferencia. Este será una función (tc gamma ns e), donde i) gamma es un contexto. Cuando el chequeador es invocado debe estar inicializado con los tipos de los identificadores predefinidos. ii) ns provee nombres frescos de variables de tipos iii) e es la expresión a ser chequeada El valor retornado será un objeto de la mónada E, el que en caso de éxito retornará un par (phi,t), donde i) phi es una sustitución definida sobre las variables no-genéricas de gamma ii) t es un tipo derivado para la expresión e, en el contexto (sub_ctx phi gamma). Será un punto fijo de phi.
Inferencia de tipos (2) Definiremos la función tc por inducción en la estructura de las expresiones, con una clausula diferente por cada una de ellas: tc :: Context -> NameSupply -> Exp -> E (Subst,Texp) tc gamma ns (Var x) = tcvar gamma ns x tc gamma ns (Ap e1 e2) = tcap gamma ns e1 e2 tc gamma ns (Lambda x e) = tclambda gamma ns x e tc gamma ns (Let decls e) = tclet gamma ns (map fst decls) (map snd decls) e tc gamma ns (LetRec decls e) = tcletrec gamma ns (map fst decls) (map snd decls) e
Funciones auxiliares add_decls :: Context -> NameSupply -> [Vname] -> [Texp] -> Context add_decls gamma ns xs ts = (zip xs schemes)++gamma where schemes = map (genbar nongeneric ns) ts nongeneric = nonGenericOfCtx gamma genbar u n t = Scheme (map snd al) t' where al = zip scvs (name_sequence n) scvs = bar (nub (tvars_in t)) u t' = sub_type (al2subst al) t tcl :: Context -> NameSupply -> [Vexp] -> E(Subst,[Texp]) tcl gamma ns [] = return(id_subst,[]) tcl gamma ns (e:es) = do (phi,t) <- tc gamma ns1 e (psi,ts) <- tcl (sub_ctx phi gamma) ns0 es return (subst_comp psi phi,(sub_type psit):ts))) where (ns0,ns1) = split ns
Chequeando variables Cuando chequeamos una variable x en un cierto contexto gamma, con nombres ns, miramos el esquema de tipos asociado a la variable en gamma. Retornamos una nueva instancia del esquema donde las variables genéricas han sido reemplazadas por variables frescas tcvar :: Context -> NameSupply -> Vname -> E(Subst,Texp) tcvar gamma ns x = return(id_subst,new_instance ns (val gamma x)) new_instance :: NameSupply -> TypeScheme -> Texp new_instance ns (Scheme scvs t) = sub_type phi t where phi = al2subst (zip scvs (name_sequence ns)) al2subst al x = if (elem x (dom al)) then Tvar (val al x) else Tvar x
Chequeando aplicaciones Cuando chequeamos una aplicación (Ap e1 e2), primero construímos una sutitución phi que resuelva las restricciones sobre e1 y e2. Supongamos que t1y t2 fueron derivados respectivamente. Luego tratamos de construír una extensión de phi que satisfaga el requerimiento adicional de que t1 = t2-> t’, donde t’ es una variable fresca. tcap :: Context -> NameSupply -> Exp -> Exp -> E(Subst,Texp) tcap gamma ns e1 e2 = do (phi,[t1,t2]) <- tcl gamma (deplete ns) [e1,e2]) let x = next_name ns psi <- unify phi (t1, arrow t2 (Tvar x)) return (psi,psi x)
Chequeando abstracciones Cuando chequeamos una abstracción (Lambda x e) asociamos x con un esquema de la forma (Scheme [] (Tvar tvn)), donde tvn es una variable fresca. Como este esquema no tiene variables genéricas las distintas ocurrencias de x en e serán asignadas el mismo valor de la variable de tipo. tclambda :: Context -> NameSupply -> Vname -> Vexp -> E (Subst,Texp) tclambda gamma ns x e = do (phi,t) <- tc gamma' ns' e return (phi,arrow (phi tvn) t)) where gamma' = new_bvar(x,tvn) : gamma ns' = deplete ns tvn = next_name ns new_bvar (a,b) = (a,Scheme [] (Tvar b))
Chequeando expresiones let Cuando chequeamos una expresión (Let [(x,e)] e’), primero chequeamos las partes derechas de las definiciones. Luego tenemos que actualizar el contexto para asociar los esquemas apropiados con los nombres de las definiciones. tclet :: TypeEnv -> NameSupply -> [Vname] -> [Exp] -> Eexp -> E (Subst,Texp) tclet gamma ns xs es e = do (phi,ts) <- tcl gamma ns1 es (psi, t) <- tc (add_decls (sub_ctx phi gamma) (fst (split ns0)) xs ts) (snd (split ns0)) e) return (subst_comp phi psi, t) where (ns0,ns1) = split ns
Chequeando expresiones letrec Para chequear expresiones de la forma (Letrec [(x,e)] e’) seguiremos los siguientes pasos: i) Asociaremos nuevos esquemas de tipos a los nombres de las definiciones, estos esquemas no incluirán variables genéricas. ii) Chequearemos las partes derechas de las definiciones. Si tenemos éxito, este paso generará una sustitución y una lista de tipos, los que pueden ser derivados para las expresiones si el contexto es restringido por la sustitución. iii) Unificaremos los tipos derivados para las partes derechas con los esquemas asociados a las variables, de acuerdo a las restricciones impuestas por la sustitución. Con esto respetamos el criterio de mismo tipo en parte derecha para todas las ocurrencias de los nombres. iv) Procedemos como lo hicimos con expresiones let.
Chequeando patterns y case Al chequear una expresión (case e of p1 -> e1 ... pn -> en) tenemos que asegurar lo siguiente: i) los patrones son lineales ii) la expresión e tiene un tipo T iii) todos los patrones tienen el mismo tipo T iv) todas las expresiones ei tienen un mismo tipo T´ v) el tipo de cada expresión ei es derivado en un contexto que considere el tipo del patron pi (componentes) |- (p1 : T) => G1 . |- (pn : T) => Gn |- e : T ,G1|- e1 : T ... ,Gn|- en : T (case) --------------------- -------------------- |- case e of p1 -> e1 ... pn -> en : T´ donde
Chequeando patterns y case (2) |- (x : T) => [x:T] |- (C : T) ------------------------- |- (c : T) => [] |- (p1 : T1) => 1 ... . |- (pn : Tn) => n ---------------------------------------------------- |- ((p1,...,pn) : T1,...,Tn) => 1,...,n |- (p: T1) => 1 ... . |- C : T1 -> T --------------------------------------------- |- ( C p : T) => 1