El patron (n+k) en Haskell a partir de la versión 7.0.3

Introducción

Con las versiones de Haskell provistas en los Ubuntu anteriores a Ubuntu 11.10, los programadores de Haskell han venido usando un truco permitido, aunque no muy ortodoxo, que era incluir el patrón (n+k) en las definiciones de funciones, a la izquierda del igual definitorio. Por ejemplo, la implementación del factorial era entendida por muchos así:

factorial :: Integer -> Integer
factorial 0 = 1
factorial (n+1)  =  (n+1) * factorial n

en lugar de:

fact :: Integer -> Integer
fact 0 = 1
fact n  =  n * fact (n-1)

El lector se preguntará intrigado por la diferencia entre factorial y fact. Es muy sutil, tanto que ni algunos desarrolladores la captan … y todo se debe a un comportamiento sui géneris del intérprete/compilador. Resulta que al intentar calcular factorial (-1) el diálogo que se producía antes, por ejemplo bajo GHCi version 6.10.4, era:

*Main> factorial (-1)
*** Exception: factorial.hs:(2,0)-(3,36): Non-exhaustive patterns in function factorial


*Main> fact (-1)
*** Exception: stack overflow

##Explicación

Con fact ocurre lo siguiente:

fact (-1)  = (-1) * fact (-2)
           = (-1) * (-2) * fact (-3)
           = etc ... por siempre

Con factorial ocurría algo distinto. Al intentar calcular factorial (-1) el intérprete busca si (-1) unifica con el argumento de la primera línea, y esto no ocurre porque -1 es distinto de 0; así pués intenta ver si (-1) unifica con (n+1) para algún valor de n, pero en las antiguas versiones (n+k), con k natural fijo pero arbitrario, únicamente unificaba con valores enteros superiores o iguales a k. Por tanto, (-1) no unificaba con el patrón de ninguna de las líneas definitorias de factorial y, siendo -1 una instancia del tipo Integer, la definición de factorial debería estar incompleta, de ahí el mensaje “Non-exhaustive patterns in function factorial”.

Total, lo que hacía el programador era optimizar el esfuerzo al escribir código y conjurar el peligro de caer en un bucle infinito, promoviendo una definición incompleta cuya excepción siempre se podría capturar.

Los desarrolladores, creemos que con buen criterio, han impedido estas estratégias y han impedido definiciones como la de factorial en beneficio de definiciones como las de fact. De hecho, si con GHCi, version 7.0.3 intentamos interpretar un fichero que contuviera a factorial, se produciría un error como el siguiente:

$ ghci factorial.hs
GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( factorial.hs, interpreted )

factorial.hs:3:12: Parse error in pattern: n + 1
Failed, modules loaded: none.
Prelude> 

donde se dice explícitamente que no admite el patrón (n+1).

Problema

Creemos que lo adecuado y aconsejable es programar sin el patrón (n+k) dando definiciones exhaustiva, con mensajes de error cuando sea conveniente; pero … ¿qué hacemos con lo ya programado?

Para programas antiguos concebidos con esas estratégias, programas que no estamos dispuestos a modificar pues podría suponer un gran esfuerzo: ¿qué solución hay, si la hay?

Solución

Pues sí la hay y la hemos encontrado. Mejor dicho, conocemos dos soluciones:

1 La primera sería incluir en la cabecera del fichero el siguiente texto:

-- posible comentario
{-# LANGUAGE NPlusKPatterns #-}
module Main where

con lo que quedaría

-- posible comentario
{-# LANGUAGE NPlusKPatterns #-}
module Main where


factorial :: Integer -> Integer
factorial 0 = 1
factorial (n+1)  =  (n+1) * factorial n

que sería admitido por el nuevo ghci sin error. No es necesario poner aquí Main en module Main where, podríamos haber puesto otro nombre, pero dejo este para sugerir que podríamos estar ante el módulo main de un proyecto, que podría ser compilado también con ghc.

2 Al compilar el fichero factorial.hs que contiene, o llama, a la función factorial, haríamos lo siguiente:

ghci -XNPlusKPatterns factorial.hs

Creemos que también funciona

ghc  -XNPlusKPatterns .... factorial.hs

En realidad, cualquiera de las dos soluciones lo que hace es forzar al intérprete/compilador de Haskell a volver al pasado, volver a los esquemas que sí admitían el patrón definitorio (n+k).

Y … esto es todo por hoy.