Hola.
Primero comentarte que tus razonamientos no son incorrectos, pero te estás enfrentando a problemas que aparecen cuando uno se ensucia las manos y programa :) : un detalle de la implementación de GHC y una cuestión de sintaxis.
Voy por partes:
Respecto al error:
• Couldn't match type ‘Integer’ with ‘Int’
Expected type: Estudiante
Actual type: ([Char], Integer, Integer,
[([Char], Integer, Integer)])
El problema acá es que en una declaración como
a1 = ("c1",1, 5)
Los literales (si no hay mas información de contexto) cuando vienen compilados desde un módulo, en el intérprete (ghci) se infieren de tipo Integer
. Esto es una decisión de diseño de los implementadores de GHC y hay razones para que sea así, pero no vamos a entrar en detalles en el curso (usualmente se llama "type defaulting"). Cuando se define a los ai en el intérprete la cosa funciona porque se infiere el tipo más general de a1
que es (Num b, Num c) => ([Char], b, c)
(igual que si llamaras a cursoNota con e como argumento dentro del módulo que definiste).
Importante: cuando se te pregunte por el tipo más general de la expresión a1
tal y como la definiste, es ese, el polimórfico. El "type defaulting" es una excepción en la implementación de GHC, nada más. Tranquilo igual si no sabés todavía inferir ese tipo por tu cuenta, es algo que vas a ir aprendiendo en el correr de las semanas.
Entonces a1
tiene tipo (String,Integer,Integer)
, y al eventualmente llamar cursosNota
con ese argumento los tipos no coinciden.
Si se anotan los tipos no va a existir ese problema, por ejemplo
a1 :: Curso
a1 = ("c1",1, 5)
(y lo mismo para a2
, a3
)
O más compacto:
a1 = ("c1",1, 5) :: Curso
Ahí la información del tipo la provee el programador, y tu ejemplo pasa bien el chequeo de tipos. Tan solo anotando en tipo de e
ya es suficiente (el compilador infiere los tipos de los ai
a partir de esa expresión).
Otra opción es usar tipos algebraicos, por ejemplo definimos:
data Curso = Curso String Int Int
...
...
a1 = Curso "c1" 1 5
en este caso no vas a tener el problema aunque no anotes los tipos, porque la presencia del constructor Curso da la información necesaria al compilador (el problema pedía usar tuplas, no me estoy olvidando de eso, esto es solo para complementar).
Tu idea de anotar solo subexpresiones, como en:
a1 = ("c1", 1: Int, 5: Int)
es buena, pero hay un error de sintaxis (dos puntos, en vez de cuatro puntos). El operador :
es la concatenación de listas, el compilador cree que 1 : Int
es un valor, entonces Int
es un valor, y como empieza con mayúscula debe ser un constructor, que no está en scope (eso es lo que quiere decir el error que muestra).