coldwa.st
Todas las guíasProgramaciónWebDatosHerramientasBases de datosHaskellConceptosCabal y buildsToolchainCompiladorRendimientoEditor y HLS

Haskell · Cabal · gestión de paquetes

¿Cómo se añade una dependencia en Cabal?

Por ColdwastActualizado el 4 de agosto de 20267 min de lectura#haskell#cabal#paquetes
Altas pilas de libros de segunda mano llenando el pasillo de una librería
Pilas de libros de segunda mano abarrotan el pasillo de una librería, apiladas más alto que las estanterías del fondo.

La respuesta corta es que una dependencia no se añade con un comando, sino con una línea en un fichero. Un paquete Haskell declara lo que necesita en su fichero .cabal, bajo build-depends, y todo lo demás se deriva de esa declaración.

Conviene decirlo con claridad, porque quien llega desde npm o Cargo busca un comando install que modifique un manifiesto, y luego se pregunta por qué nada de lo instalado resulta visible para el compilador.

El campo que de verdad importa: build-depends

Abre tu fichero .cabal y localiza la estrofa de aquello que estás construyendo: una library, un executable o una test-suite. Cada una lleva su propio build-depends, y ese es el punto que más se pasa por alto.

library
  exposed-modules:  MyApp.Core
  build-depends:    base ^>=4.18
                  , containers ^>=0.6
                  , text ^>=2.0
  default-language: Haskell2010

Añadir un paquete consiste en añadir una línea aquí. La coma al principio de línea es una convención, no un requisito: existe para que añadir o quitar una línea toque una sola.

Las dependencias son de cada estrofa. Un paquete que necesita tu suite de pruebas no pertenece al build-depends de la biblioteca, y un paquete que necesita tu biblioteca no queda automáticamente disponible para tu ejecutable, salvo que este dependa de la propia biblioteca. Es más estricto que en la mayoría de ecosistemas, y es deliberado: mantiene las herramientas de prueba fuera de lo que tus usuarios tienen que compilar.

Qué significa realmente el operador caret

El ^>= que se ve por todas partes no es decorativo. Es una abreviatura ligada a la Package Versioning Policy de Haskell, la PVP, y entenderla elimina casi todas las conjeturas sobre las cotas.

Bajo la PVP una versión se lee A.B.C.D, donde A.B en conjunto forman la versión mayor. Los cambios que rompen incrementan A o B. Las adiciones que no pueden romper código existente incrementan C. Así, ^>=1.2.3 significa al menos 1.2.3 y por debajo de la siguiente mayor, lo que se expande a >=1.2.3 && <1.3.

La consecuencia sorprende: en Haskell, pasar de 1.2 a 1.3 es un salto mayor, no menor. Si arrastras el modelo mental del versionado semántico, donde solo el primer número es mayor, escribirás cotas mucho más laxas de lo que pretendías.

Tablillas de madera idénticas montadas en pequeñas estructuras, cada capa apoyada sobre la inferior
Tablillas de madera idénticas montadas en pequeñas estructuras, cada capa apoyada sobre la de abajo.

Por qué cabal update va primero

Cabal resuelve dependencias contra una copia local del índice de paquetes de Hackage. Si esa copia está obsoleta, un paquete publicado la semana pasada sencillamente no existe para tu máquina, y obtienes un error de resolución que se lee como si el nombre del paquete estuviera mal.

cabal update
cabal build

Ejecuta cabal update cuando un paquete que sabes que existe no aparezca, y tras cualquier pausa larga entre sesiones. Es lo primero que hay que probar y no cuesta nada.

Leer un fallo de resolución sin alarmarse

Cuando el solucionador no puede satisfacer todas las restricciones a la vez, informa del conflicto en lugar de elegir en silencio. El mensaje es denso, pero dice algo concreto: dos de tus dependencias quieren rangos incompatibles de una tercera.

Las opciones honestas son pocas y conviene conocerlas antes de recurrir a una opción de línea de comandos:

Relajar una cota que pusiste tú. Si la cota demasiado estrecha está en tu propio fichero .cabal, ampliarla es legítimo, siempre que después compiles y pruebes en vez de suponerlo.

Elegir otra versión del paquete que estás añadiendo. A menudo una versión anterior de la nueva dependencia encaja con las restricciones que tu proyecto ya tiene.

Usar --allow-newer a sabiendas y de forma temporal. Le dice al solucionador que ignore las cotas superiores declaradas por otros paquetes. Esas cotas las escribieron mantenedores que tenían un motivo, así que trátalo como un diagnóstico que revela dónde está el conflicto real, no como un arreglo que se commitea.

Fijar lo que has resuelto

La resolución elige versiones en un momento dado. Para hacer ese momento reproducible, fíjalo:

cabal freeze

Esto escribe un fichero cabal.project.freeze que registra las versiones exactas elegidas. Commitéalo cuando quieras una compilación que se comporte igual el mes que viene, que es la expectativa habitual en una aplicación. Las bibliotecas son el caso contrario: deben permanecer flexibles para que quien dependa de ellas pueda resolver con libertad.

Para un proyecto que abarque varios paquetes, o que necesite una dependencia de código fuente ausente de Hackage, esa configuración pertenece a cabal.project y no al fichero .cabal. Mantener ambos separados es la forma más clara de orientarse: el fichero .cabal describe un paquete y cabal.project describe cómo compilar un conjunto de ellos.

En resumen

Añade una dependencia agregando una línea a build-depends, en la estrofa que realmente la necesita. Acótala con ^>=, recordando que bajo la PVP los dos primeros números forman juntos la versión mayor. Ejecuta cabal update antes de culpar a un paquete por no existir. Fija las aplicaciones y deja sueltas las bibliotecas.

Si todavía estás eligiendo herramientas, nuestra comparación entre Stack y Cabal aborda esa decisión, y la guía de instalación de GHCup monta la cadena de herramientas que este artículo da por instalada.

El campo build-depends, la expansión del operador caret y la regla de la versión mayor A.B siguen la guía de usuario de Cabal y la Package Versioning Policy de Haskell, verificadas al momento de escribir. La disponibilidad de comandos varía según tu versión de cabal-install: ejecuta cabal --version y consulta la guía de tu versión antes de apoyarte en un subcomando concreto.