types - tipos - ¿Por qué escribir declaraciones de tipo en Haskell?
multiplicar haskell (5)
Soy nuevo en Haskell y estoy tratando de entender por qué uno necesita escribir declaraciones de tipo. Dado que Haskell tiene inferencia de tipo, ¿cuándo necesito la primera línea? GHCI parece generar una salida correcta con el uso de '': t''
El único ejemplo que encontré hasta ahora que parece necesitar una declaración es el siguiente.
maximum'' :: (Ord a) => [a] -> a
maximum'' = foldr1 max
Sin embargo, si agrego la declaración de la bandera "-XNoMonomorphismRestriction" no es necesaria otra vez. ¿Hay situaciones específicas en las que la inferencia de tipos no funciona y uno necesita especificar tipos?
Ya que podría tener un error en la declaración de tipo y no tener un beneficio directo, preferiría no escribirlo. Nuevamente, acabo de comenzar a aprender Haskell, así que corríjame si me equivoco, ya que quiero desarrollar buenos hábitos.
EDITAR: Resulta que la inferencia de tipos es una sección de espada de doble filo del libro Real World Haskell tiene una buena discusión de este tema.
Considera read "5"
. ¿Cómo puede Haskell saber el tipo de read "5"
? No puede, porque no hay manera de resolver el resultado de la operación, ya que la read
se define como (Read a) => String -> a
. a
no depende de la cadena, por lo que debe usar el contexto.
Sin embargo, generalmente el contexto es algo como Ord
o Num
por lo que es imposible determinarlo. Esta no es la restricción de monomorfismo, sino otro caso que nunca puede manejarse adecuadamente.
Ejemplos:
No funciona:
read "0.5"
putStrLn . show . read $ "0.5"
Funciona:
read "0.5" :: Float
putStrLn . show . (read :: String -> Float) $ "0.5"
Estos son necesarios porque la instancia predeterminada de Show
, si recuerdo correctamente, es Int
.
Por lo general es porque hace que sea más fácil de leer y, a veces, más fácil de escribir. En un lenguaje fuertemente tipado como Haskell, a menudo se encontrará haciendo funciones que toman algunos tipos y producen otro tipo y se encuentran confiando en cuáles son estos tipos en lugar de sus nombres. Una vez que se haya acostumbrado a cómo funciona el sistema de tipos, puede aclarar lo que pretende hacer y el compilador puede atraparlo si ha hecho algo mal.
Pero esto es una cosa de preferencia. Si está acostumbrado a trabajar en idiomas tipificados dinámicamente, puede encontrar que especificar ningún tipo sea más fácil que especificarlos. Son solo dos formas diferentes de usar el sistema de tipos fuerte que proporciona Haskell.
Luego, hay ocasiones en que la inferencia de tipos no funciona, como el ejemplo de "lectura" que dio otra respuesta. Pero esas son definiciones de tipo en línea en lugar de la definición de tipo para una función.
Una cosa importante que realmente no he visto cubierta en ninguna respuesta es que a menudo realmente escribirá sus definiciones de tipo y firmas de tipo, antes de escribir cualquier código real. Una vez que haya completado esa "especificación", se comprobará su implementación mientras la escribe, lo que facilitará la detección de errores antes de que el compilador compruebe que sus tipos coinciden. Si sabe, por ejemplo, que algo debe tener una firma Int -> Int -> [a] -> [a]
pero, al escribirlo, en lugar de crear una instancia de dos parámetros x
e y
, crea una instancia de un solo parámetro x
por accidente y úselo dos veces, el compilador detectará el error en el punto en el que definió la función, en lugar del punto en el que intentó usarla como se suponía que debía usarla.
Tranquilidad A veces es bueno asegurarse de que el compilador esté de acuerdo con tu percepción del tipo de función que debe tener. Si el tipo inferido no se unifica con tu tipo dado, entonces el compilador te gritará. Una vez que se familiarice con el sistema de tipos, encontrará que las firmas de tipos opcionales pueden ser de gran ayuda para su confianza en la codificación.
- cuando tienes grandes programas de Haskell, tener firmas de tipo a menudo te da mejores mensajes de error del compilador
- En algún momento puede derivar lo que hace una función a partir de su nombre y su firma.
- a menudo, una función es mucho más comprensible con las firmas de tipo, por ejemplo, si utiliza currying
- incluso escribir programas se hace más fácil, a menudo comienzo con firmas de tipo y la mayoría de las funciones declaradas como
undefined
. Todo se compila, sé que mi idea parece no encajar demasiado mal. Luego sigo y sustituyoundefined
por código real.