haskell where-clause type-signature

haskell - ¿Por qué es tan poco común utilizar firmas de tipo en las cláusulas where?



where-clause type-signature (4)

¿Ayuda al compilador a optimizar, o es simplemente un trabajo adicional para agregar firmas de tipo adicionales? Por ejemplo, uno ve a menudo:

foo :: a -> b foo x = bar x where bar x = undefined

Más bien que:

foo :: a -> b foo x = bar x where bar :: a -> b bar x = undefined

Si omito la firma de tipo de nivel superior, GHC me da una advertencia, por lo que si no recibo advertencias, estoy bastante seguro de que mi programa es correcto. Pero no se emiten advertencias si omito la firma en una cláusula where.


A menudo, where declaraciones se utilizan para cosas cortas y locales, que tienen tipos simples o tipos que se deducen fácilmente. Como resultado, no hay beneficio para el humano o compilador para agregar el tipo.

Si el tipo es complejo, o no se puede inferir, es posible que desee agregar el tipo.

Si bien las firmas de tipo monomórfico pueden hacer que las funciones de nivel superior sean más rápidas, no es tanto una ganancia para las definiciones locales en las cláusulas where , ya que GHC alineará y optimizará las definiciones en la mayoría de los casos de todos modos.


A menudo, las definiciones en where están las cláusulas son para evitar repetirse si una sub-expresión ocurre más de una vez en una definición. En tal caso, el programador piensa en la definición local como un simple complemento para escribir las subexpresiones en línea. Por lo general, no escribirá explícitamente las subexpresiones en línea, por lo que tampoco escribirá la definición de where . Si lo hace para ahorrar en la escritura, entonces la declaración de tipo eliminará todos sus ahorros.

Parece bastante común introducir a los estudiantes de Haskell con ejemplos de esa forma, por lo que siguen pensando que el "estilo normal" es no dar declaraciones de tipo para definiciones locales. Al menos, esa fue mi experiencia aprendiendo Haskell. Desde entonces he descubierto que muchas de mis funciones son lo suficientemente complicadas como para necesitar un bloque where se vuelven más bien inescrutables si no conozco el tipo de las definiciones locales, por lo que trato de equivocarme al escribirlas ahora; incluso si creo que el tipo es obvio mientras escribo el código, puede que no sea tan obvio cuando lo leo después de no haberlo mirado por un tiempo. ¡Un poco de esfuerzo para mis dedos casi siempre es superado incluso por uno o dos casos de tener que ejecutar inferencia de tipo en mi cabeza!

La respuesta de Ingo da una buena razón para no dar deliberadamente un tipo a una definición local, pero sospecho que la razón principal es que muchos programadores han asimilado la regla general de que se proporcionen declaraciones de tipo para las definiciones de nivel superior, pero no para las definiciones locales. Ellos aprendieron Haskell.


Agregar una firma de tipo puede hacer que su código sea más rápido. Tomemos por ejemplo el siguiente programa (Fibonacci):

result = fib 25 ; -- fib :: Int -> Int fib x = if x<2 then 1 else (fib (x-1)) + (fib (x-2))

  • Sin la anotación en la segunda línea, toma 0.010 seg. correr.
  • Con la anotación Int -> Int , toma 0.002 seg.

Esto sucede porque si no dices nada sobre fib , se escribirá como fib :: (Num a, Num a1, Ord a) => a -> a1 , lo que significa que durante el tiempo de ejecución, estructuras de datos adicionales ( Los "diccionarios") deberán pasarse entre funciones para representar las clases de tipo Num / Ord .


Existe una clase de funciones locales cuyos tipos no se pueden escribir en Haskell (es decir, sin usar extensiones GHC de fantasía). Por ejemplo:

f :: a -> (a, Int) f h = g 1 where g n = (h, n)

Esto se debe a que si bien la firma de tipo a en f es polimórfica vista desde fuera f , esto no es así desde f . En g , es solo un tipo desconocido, pero no de cualquier tipo, y (estándar) Haskell no puede expresar "el mismo tipo que el primer argumento de la función en el que está definido" en su lenguaje de tipo.