Programación funcional Inferencia de tipos manual para programas Haskell J. Sánchez Hernández Enero de 2007 J. Sánchez Hernández Programación funcional Inferencia de tipos manua El sistema de tipos de Haskell Es una variante del propuesto R. Milner en 1978 (con antecedentes en J.R. Hindley, 1969). • admite polimorfismo paramétrico • toda expresión tiene un tipo principal, o bien no es tipable • hay un algoritmo de inferencia para deducir el tipo principal de cualquier expresión (o decidir que está al tipada). J. Sánchez Hernández Programación funcional Inferencia de tipos manua Tipo de una expresion Se siguen los siguientes pasos: 1 Decorar de la forma “id :: <tipo>” cada identificador id (de variable o de función) y cada resultado de una aplicación (bien sea de función o de constructora) con una instancia fresca de su tipo si se conoce o con una variable fresca a en caso contrario, teniendo en cuenta que: • las apariciones de una variable en la misma expresión han de estar decoradas con el mismo tipo (puesto que deben tener el mismo tipo) • cada aparición de una función con tipo conocido ha de tener una instancia fresca de su tipo (polimórfico). • las tuplas (e1 , . . . , en ) pueden decorarse directamente con (a1 , . . . , an ) 2 Generar las ecuaciones según la siguiente regla: e0 :: t0 e1 :: t1 . . . en :: tn (e0 e1 . . . en ) :: t t0 = t1 → . . . t n → t J. Sánchez Hernández Programación funcional Inferencia de tipos manua Tipo para una regla de función 3 Resolver las ecuaciones generadas mediante un algoritmo de unificación, teniendo en cuenta que: • las tuplas y listas (y cualquier otra constructora) se descomponen de manera ordinaria. Por ejemplo la ecuación (t1 , t2 ) = (t10 , t20 ) se descompone en las dos ecuaciones t1 = t10 , t2 = t20 y [t1 ] = [t2 ] se descompone en t1 = t2 . • el constructor de tipo → se comporta como una constructora mas, i.e., una ecuación de la forma t1 → t2 = t10 → t20 se descompone en dos ecuaciones t1 = t10 , t2 = t20 Si el algoritmo tiene éxito proporciona el tipo principal para la expresión (y cada una de sus subexpresiones). Si falla, la expresión está mal tipada. Nota: recordemos que la aplicación asocia por la izquierda y el operador → por la derecha. J. Sánchez Hernández Programación funcional Inferencia de tipos manua Algoritmo de unificación de Martelli-Montanari Aplicar alguna de las siguientes reglas mientras sea posible: 1. S ∪ {X = X } S 2. S ∪ {f (t1 , . . . , tn ) = f (s1 , . . . , sn )} S ∪ {t1 = s1 , . . . , tn = sn } 3. S ∪ {f (t1 , . . . , tn ) = g (s1 , . . . , sm )} si f 6= g (ó n 6= m) FALLO, 4. S ∪ {X = t} S[X /t] ∪ {X = t}, si X 6∈ var (t), X ∈ var (S), X 6= t 5. S ∪ {X = t} FALLO, si X ∈ var (t) 6. S ∪ {t = X } S ∪ {X = t}, si t 6∈ V Este algoritmo proporciona u.m.g.’s idempotentes J. Sánchez Hernández Programación funcional Inferencia de tipos manua Ejemplo Supongamos la expresión: True == (False || (not False)) Sabemos que: (==) :: a → a → Bool y que not :: Bool → Bool, (||) :: Bool → Bool → Bool. Para facilitar las cosas ponemos la expresión en forma prefija (==) True ((||) False (not False)) • Decoramos: ((==) :: a → a → Bool True :: Bool ((||) :: Bool→Bool→Bool False :: Bool (not :: Bool→Bool False :: Bool) :: b ) :: c ) :: d J. Sánchez Hernández Programación funcional Inferencia de tipos manua • Ecuaciones: Bool → Bool = Bool → b Bool → Bool → Bool = Bool → b → c a → a → Bool = Bool → c → d • Resolvemos: a = Bool, b = Bool, c = Bool, d = Bool Otro ejemplo: map (+1) [2] Para facilitar las cosas utilizamos notación prefija en todos los operadores y la lista [2] la escribimos de la forma (:) 2 [], sabiendo que: (:) :: a → [a] → [a] J. Sánchez Hernández Programación funcional Inferencia de tipos manua Inferencia de tipos para reglas de función f p = |{z} e |{z} cabeza cuerpo • Se decora la cabeza y el cuerpo, y se plantean las ecuaciones del mismo modo, pero con una ecuación adicional entre el tipo de la cabeza y el del cuerpo. • Si hay guardas también se decoran y se plantean las ecuaciones oportunas. Ejemplo: cua x = x ∗ x (suponiendo (∗) :: Int → Int → Int) • Decoramos: (cua::a x::b)::c = ((*)::Int→ Int→ Int x::b x::b)::d • Ecuaciones: {a = b → c, Int → Int → Int = b → b → d, c = d} • Resolvemos: b = Int, c = Int, d = Int, a = Int → Int (luego cua :: Int → Int) J. Sánchez Hernández Programación funcional Inferencia de tipos manua Tipo para funciones • Se decora cada una de las reglas de función, anotando el mismo tipo en todas las reglas para todas las apariciones de la función que se está definiendo. • Si el algoritmo de unificación tiene éxito el tipo obtenido para la función es el tipo más general. Si no tiene éxito, la función está mal tipada. • Si la función tiene tipo declarado se comprueba que dicho tipo sea una instancia del tipo inferido, i.e., que el tipo inferido es más general que el declarado. Si es ası́, se anota el tipo declarado para la función, en otro caso, error. J. Sánchez Hernández Programación funcional Inferencia de tipos manua Tipo para lambda-expresiones λx → e • se decoran x, e y la propia lambda expresión del modo habitual, (λx :: a → edecorada :: b) :: c • se plantean las ecuaciones resultantes de edecorada y una ecuación adicional c = a → b • se resuelve del modo ordinario Las λ-expresiones con más de un argumento se reducen al caso de un argumento. Por ejemplo: λx y → e) se decora como (λx :: a1 → (λy :: a2 → edecorada :: a3 ) :: a4 ) :: a5 y se añaden las ecuaciones adicionales: a4 = a2 → a3 , a5 = a1 → a4 J. Sánchez Hernández Programación funcional Inferencia de tipos manua Tipo para expresiones con let let <defs. locales> ... in e 0 Las definiciones locales: • se decoran del modo habitual, se plantean las ecuaciones y se resuelven. Para la expresión externa: • Se decora e 0 utilizando los tipos inferidos en el paso anterior: • el tipo de las variables globales queda congelado (no se toman instancias frescas) • para el resto de variables/funciones se toman instancias frescas • Se plantean y resuelven las ecuaciones del modo habitual. J. Sánchez Hernández Programación funcional Inferencia de tipos manua Ejemplo Sea h x y = (x, y ) con el tipo ya inferido h :: a → b → (a, b) y f x = let g y =hx y in (x, g 3, g x, g True) • Decoramos la definición local: (g :: a1 y :: a2 ) :: a3 = (h :: a4 → a5 → (a4 , a5 ) x :: a6 y :: a2 ) :: a7 • Ecuaciones: a1 = a2 → a3 , a4 → a5 → (a4 , a5 ) = a6 → a2 → a7 , • Resolvemos: a1 = a2 → (a4 , a2 ), a3 = (a4 , a2 ), a6 = a4 , a5 → a2 , a3 = a7 a7 = (a4 , a2 ) Lo que nos interesa: g :: a2 → (a4 , a2 ) y x :: a4 , con a4 congelada g :: a2 → (a4∗ , a2 ) J. Sánchez Hernández x :: a4∗ Programación funcional Inferencia de tipos manua Ahora procedemos con la expresión externa: • Cada aparición de g utiliza una instancia fresca del tipo inferido • x :: a4 es variable global (externa) a4∗ queda congelado • Decorando: (f :: c0 x :: a4∗ ) :: c1 = ( x :: a4∗ , (g :: b1 → (a4∗ , b1 ) 3 :: Int) :: a8 , (g :: b3 → (a4∗ , b3 ) x :: a4∗ ) :: a9 , (g :: b5 → (a4∗ , b5 ) True :: Bool) :: a10 ) :: (c2 , c3 , c4 , c5 ) • Resolviendo tenemos f :: a4∗ → (a4∗ , (a4∗ , Int), (a4∗ , a4∗ ), (a4∗ , Bool)) Ejercicio: f 0 x = let g y = h x y in (fst (g 3) + fst (g x), g True) J. Sánchez Hernández Programación funcional Inferencia de tipos manua Tipo para programas • En general, si una función f utiliza otra g en su definición el tipo de g deberá inferirse antes que el tipo de f . • ... pero puede haber funciones mútuamente recursivas, cuyo tipo ha de inferirse simultáneamente. Esto hace que el algoritmo sea algo más sofisticado de lo que cabrı́a esperar: • Construir el grafo de dependencias funcionales: los nodos son los nombre de función; si f utiliza g en su definición habrá un arco de f a g . • Determinar las componentes fuertemente conexas del grafo (que corresponden a definiciones mutuamente recursivas). • Calcular la ordenación topológica de dichas componentes. Los tipos se inferiran en dicho orden. J. Sánchez Hernández Programación funcional Inferencia de tipos manua Ejemplo even 0 = True even n = odd (n-1) odd 0 = False odd n = even (n-1) Recursión mutua! Los tipos se infieren a la vez: se decoran todas las reglas y se plantean las ecuaciones correspondientes. J. Sánchez Hernández Programación funcional Inferencia de tipos manua Inferencia de tipos con sistema de clases El algoritmo es esencialmente el mismo, pero • se anotan las cualificaciones/restricciones de tipo al decorar las expresiones • se propagan al resolver las ecuaciones. Además cuando una misma variable está afectada por dos restricciones de clase se conserva la más restriciva • Eq a y Ord a se simplificarı́a a Ord a (todo tipo ordenado es en particular un tipo con igualdad) J. Sánchez Hernández Programación funcional Inferencia de tipos manua Ejemplo cua x = x ∗ x, sabiendo (∗) :: Num a ⇒ a → a → a • Decoramos: (cua :: a1 x :: a2 ) :: a3 = ((∗) :: Num a4 ⇒ a4 → a4 → a4 x :: a2 x :: a2 ) :: a3 • Ecuaciones + Restricciones a1 = a2 → a3 , a4 → a4 → a4 = a2 → a2 → a3 Numa4 • Resolvemos: a1 = a2 → a2 → a2 , a3 = a2 , a4 = a2 Num (a2 , a3 , a4 ) Tenemos cua :: Num a2 :: a2 → a2 → a2 J. Sánchez Hernández Programación funcional Inferencia de tipos manua