Hola,
Lo que sucede es que (dup dup) corresponde a la duplicación de la función dup. Como dup es polimórfica,
dup :: a -> (a,a)
dup x = (x,x)
le puedo pasar cualquier cosa como argumento, en particular la propia función dup. Por lo tanto (dup dup) retorna el par (dup,dup) que tiene tipo (a -> (a,a), a -> (a,a)). A pesar que el par resultante contiene funciones en sus componentes, no lo puedo aplicar directamente a un valor. Puedo aplicar alguna de esas funciones del par si primero las extraigo del par. Por ejemplo, puedo hacer lo que había dicho Marcos:
fst (dup dup) 5 retorna (5,5)
o sea, extraemos la función que está en la primera componente del par (dup,dup) haciendo fst (dup dup), eso me da como resultado la propia función dup; por último aplico dup a 5, resultando en (5,5).
Otro ejemplo, Para poder aplicar (dup,dup) "directamete" debo definir una función que aplique pares de funciones a pares de argumentos:
app2 (f,g) (x,y) = (f x,g y)
Luego puedo hacer: app2 (dup,dup) (3,4), que retorna ((3,3),(4,4)).
Finalmente, la expresión (dup dup) es permitida debido al polimorfismo. En esa expresión estamos usando 2 instancias diferentes de dup. Voy a escribir la expresión como (dup_1 dup_2) para que sea mas sencilla la explicación. Dado que el tipo de dup :: a -> (a,a) es polimórfico, podemos tener ocurrencias de dup sobre distintos tipos "a". Supongamos que dup_2 es la instancia que manipula objetos de tipo t, por lo que dup_2 :: t -> (t,t). Si es así entonces dup_1 es la instancia de dup que está tomando como entrada objetos de tipo t -> (t,t) (o sea funciones):
dup_2 :: (t -> (t,t)) -> (t -> (t,t), t -> (t,t))
A pasarle dup_1 es que finalmente se arma la expresión (dup_1 dup_2) que retorna el par:
(dup_2,dup_2) :: (t -> (t,t), t -> (t,t))
Saludos,
Alberto.