cabal icon indicating copy to clipboard operation
cabal copied to clipboard

Updates to the cabal files created by cabal init

Open Kleidukos opened this issue 2 months ago • 20 comments

I'd like to suggest some updates to the cabal file template used by cabal init. We can discuss them separately.

Motivation

I believe that cabal should guide new projects towards writing the best Haskell code possible, without taking principled stances on things that are based in personal taste.

Disclaimer: My background is as a professional backend developer using Haskell, and I mentor new users in community spaces through their first cabal projects.

Current state of things

For the purpose of this ticket I created a new cabal project:

cabal-version:      3.4
name:               lol
version:            0.1.0.0
-- synopsis:
-- description:
license:            BSD-3-Clause
license-file:       LICENSE
-- author:
-- maintainer:
-- copyright:
build-type:         Simple
extra-doc-files:    CHANGELOG.md
-- extra-source-files:

common warnings
    ghc-options: -Wall

library
    import:           warnings
    exposed-modules:  MyLib
    -- other-modules:
    -- other-extensions:
    build-depends:    base ^>=4.18.3.0
    hs-source-dirs:   src
    default-language: GHC2024

executable lol
    import:           warnings
    main-is:          Main.hs
    -- other-modules:
    -- other-extensions:
    build-depends:
        base ^>=4.18.3.0,
        lol

    hs-source-dirs:   app
    default-language: GHC2024

test-suite lol-test
    import:           warnings
    default-language: GHC2024
    -- other-modules:
    -- other-extensions:
    type:             exitcode-stdio-1.0
    hs-source-dirs:   test
    main-is:          Main.hs
    build-depends:
        base ^>=4.18.3.0,
        lol

Changes

What does the project build?

$ cabal init
What does the package build:
   1) Library
 * 2) Executable
   3) Library and Executable
   4) Test suite
Your choice? [default: Executable] 

A. Executable without library: The current community status quo encourages cabal projects to put most of their business logic in a library component and put program initialisation in the executable's Main.hs. As such, the executable-only option does not reflect the best practices promoted by the community. The fact that it is the default option makes it even worse.

My recommendation is to remove it.

B. Test suite: I can find a reasoning as to why some projects may want to have a separate package for their test suite that gets picked up by cabal test, but highly it's unclear to beginners why they should care for such a thing. Moreover, the presence of Test suite below Library and Executable implies that the interactive wizard will not create a test suite component (since it's a different option).

My recommendation is to put an in-line explanation like (for projects who wish to separate their test suite) if we are keeping it. I could see it removed, as it does not reflect a frequently used pattern for new projects.

Common stanzas

In the last industrial & hobby projects I have worked on, the default-language is shared by all components. In fact, the current warnings name is misleading because it incurs an artificial distinction between flags that provide warnings and those that do not. I would like to suggest (based on experience) a new nomenclature:

  • extensions: Used by all components
  • ghc-options: Used by all components
  • rts-options: Used by all executable, test and benchmark components

In practice this looks like the following snippet (NOTE: The extensions and flags themselves are not suggested for inclusion in the template, they are just here as an example that I have picked verbatim from my projects):

common extensions
  default-extensions:
    DataKinds
    DeepSubsumption
    DeriveAnyClass
    DerivingStrategies
    DerivingVia
    DuplicateRecordFields
    GADTs
    LambdaCase
    NoStarIsType
    NumericUnderscores
    OverloadedLabels
    OverloadedRecordDot
    OverloadedStrings
    PackageImports
    PolyKinds
    StrictData
    TypeFamilies
    UndecidableInstances
    ViewPatterns

  default-language: GHC2021

common ghc-options
  ghc-options:
    -Wall
    -Wcompat
    -Widentities
    -Wincomplete-record-updates
    -Wincomplete-uni-patterns
    -Wpartial-fields
    -fhide-source-paths
    -Wno-unused-do-bind
    -fshow-hole-constraints
    -Wno-unticked-promoted-constructors
    -Werror=unused-imports
    -fdicts-strict
    -fmax-worker-args=16
    -fspec-constr-recursive=16
    -Wunused-packages
    -flate-specialise
    -funbox-strict-fields
    -finline-generics-aggressively
    -fexpose-all-unfoldings

common rts-options
  ghc-options:
    -rtsopts
    -threaded
    "-with-rtsopts=-N -T"

You can see that GHC options used for both emitting warnings and controlling behaviour like strict dicts, unboxing, inlining, and UX improvements like -fhide-source-paths are shared within one stanza, whereas the rtsopts relevant only for runtime are located elsewhere.

extra-doc-files, extra-source-files

It's good that CHANGELOG.md is included in extra-doc-files, but I'd like to have a README.md generated and added to this stanza.

Bounds for GHC when selecting GHC language editions

I selected the GHC2024 language edition, which I'm told needs GHC 9.10 at least. However when generating the cabal file, the "default" GHC in my path (GHC 9.6) is used to generate the bounds of base, leading to a cabal-GHC error when I type cabal build.

Since I (and most users) use GHCup to handle our toolchains, we are expected to use cabal.project to give the appropriate GHC executable name. I'm not sure what the ideal workflow looks like here.

The cabal.project limbos

Ok I'm a GHCup user, I have asked on Discord how to make cabal pick my compiler of choice, I have learned that there exists a cabal.project file that I can create (but never seen mentioned once in the guide or in the interactive wizard).

Turns out writing packages: ./ and with-compiler: ghc-9.10.1 is not enough because my test suite does not build when I run cabal build or even cabal build all. That's a big UX problem that is almost inexplicable because while the default is documented as False, it's overridden when calling cabal test.

My recommendations are:

  1. Either we enable tests: True by default, which is the usually desired behaviour: If you have created a test suite and written tests, you usually want to build it with the rest, so that cabal test does not surprise you with a second build step just for your tests.

  2. We create a cabal.project file with packages: ./ at least, and commented keys like tests, documentation and optimization. I'm specifically mentioning optimization because we want to discourage project authors to put optimisation levels in their package file, so the sooner we redirect them to cabal.project, the better it will be.


I can do the implementation but please do judge these suggestions on their own merit.

Kleidukos avatar Oct 01 '25 12:10 Kleidukos

How does this interact with --interactive? E.g. I'd expect to be asked if I want to create a cabal.project file.

The same may apply to other points.

hasufell avatar Oct 01 '25 13:10 hasufell

@hasufell Most people who run cabal init for the first time do not even know that they need a cabal.project. While I don't question the split between "package" and "project" options, they are both needed. And if users are not made aware of the "cabal.project" file, they will try to put project options in their cabal file.

Kleidukos avatar Oct 01 '25 13:10 Kleidukos

So your suggestion is to not give the user an option "I don't want cabal.project" when they run cabal init --interactive?

hasufell avatar Oct 01 '25 13:10 hasufell

@hasufell Yes

Kleidukos avatar Oct 01 '25 13:10 Kleidukos

extra-doc-files, extra-source-files

I think this is the wrong place to solve this. I think that a user who does not have a README should not be forced to make one but a user who wants a README should have it automatically included for hackage uploads (which I assume is the intention of adding it to that field)

handle our toolchains, we are expected to use cabal.project to give the appropriate GHC executable name

I think this should be opt in. Cabal can very well figure out a default executable and I personally (and many other people) use a workflow where I do not have more than one in the current environment.

Common stanzas

I agree that the common stanza created by default should be renamed, but I disagree with creating three of them, again opinionated.


I think the opinionated bits can be implemented in user space, I don't think we should force the user to adapt a workflow that isn't really "universally" agreed upon, not only beginners are using cabal init.

MangoIV avatar Oct 01 '25 13:10 MangoIV

@MangoIV Thank you for your remarks, I have several questions:

I think this is the wrong place to solve this. I think that a user who does not have a README should not be forced to make one but a user who wants a README should have it automatically included for hackage uploads (which I assume is the intention of adding it to that field)

I don't think this proposal should litigate the current mechanism, especially that we can't really tell which of (README,README.md,README.rst,README.org) is to be included if two or more of these files simultaneously exist at the root of the project. An explicit value that is pre-populated is always easier to change than having to look up where things go on the online documentation.


Cabal can very well figure out a default executable

Can it "figure out"? To me it seems that cabal can only use the ghc executable present in PATH unless instructed so by with-compiler/-w. I am not aware of any other mechanism.


I don't think we should force the user to adapt a workflow that isn't really "universally" agreed upon

We are already doing so, the current workflow only exists due to unilateral choices by previous cabal maintainers. It's become accidentally universal because of inertia.

Kleidukos avatar Oct 01 '25 13:10 Kleidukos

I agree with most suggestions.

But most importantly I strongly agree with a UX revamp for cabal init. As now the user has to enter input fourteen times to finally arrive at the end of the process, an ordeal especially for newcomers.

ffaf1 avatar Oct 01 '25 13:10 ffaf1

Like Francesco, I like most of it at a high-level and currently don't have time to put up my bikeshedding.

@ffaf1 says:

As now the user has to enter input fourteen times to finally arrive at the end of the process, an ordeal especially for newcomers.

This is horrible. I always use non-interactive, so I'd not know. I keep forgetting which one is the default (interactive or not) these days but I hope it's non-nteractive!

ulysses4ever avatar Oct 01 '25 14:10 ulysses4ever

I can confirm that $ cabal init starts the interactive mode.

Kleidukos avatar Oct 01 '25 14:10 Kleidukos

My own suggestions would be:

  1. interactive can stay default but should have only one question, what kind of project. (I do agree that the current default is a bad idea: executables should be divided into a library and a driver.)
  2. the comments have to go. (People on Discord seem to agree.) The right thing to do here is a commented example in the cabal manual with links to the field definitions, and cabal init can output (perhaps only in interactive mode) a link to it.

geekosaur avatar Oct 01 '25 18:10 geekosaur

I like the idea, but as a Haskell user I'm shocked by the number of extensions and flags that I don't know what they do.

zlonast avatar Oct 02 '25 05:10 zlonast

interactive can stay default but should have only one question, what kind of project

I don't feel that's interactive at all.

The way this is solved in other interactive UIs is that you answer the installation type at the start:

  • do whatever
  • simple installation
  • expert installation

hasufell avatar Oct 02 '25 07:10 hasufell

Hi there! I want to insert my two bits into the discussion around shaping the flag set:

DeepSubsumption

This extension is considered legacy and in fact it is: this behavior was deleted in 9.0 and restored in 9.2 to provide a fallback for projects that experience difficulties with migration. Type checker under this extension doesn't play very well with the new type level extensions that provide more precise control over instantiation.

DeriveAnyClass

This extension is unsafe by default and I would like to see it opt-in rather than opt-out.

NumericUnderscores

This extension is included into both GHC2021 and GHC2024, and your config suggests GHC2021.

DataKinds, DerivingStrategies, GADTs, LambdaCase

These extensions are included into GHC2024.

StrictData

I personally don't like extensions that silently change behavior of code and would prefer to insert some bangs manually rather than use StrictData. But that's just my personal opinion, feel free to ignore it.


Regarding warning flags I'm pretty much sure that at least couple of them is enabled by default in some recent GHC version.

s-and-witch avatar Oct 02 '25 07:10 s-and-witch

Sorry for the confusion @s-and-witch, @zlonast. The extensions and flags themselves are not suggested to be included by default, they are just here as an example. I picked that snippet verbatim from my projects.

Kleidukos avatar Oct 02 '25 07:10 Kleidukos

I find that the default "Executable" choice is a great way to start using cabal without package management overhead such as multi-package development, testing, benchmarking, and profiling workflows. I think new cabal users don’t need to think about these concerns when their goal is simply to create a first Haskell application

Also, you can set up multiple single-executable cabal files that share a common library via a cabal.project file. This is especially useful when using Nix or Docker, since it helps prevent cache misses when the single cabal file parts changes.

KovalevDima avatar Oct 02 '25 13:10 KovalevDima

Yeah, I hope that the executable-no-library option will stay: I use it all the time for little experiments that don't have to use the blessed executable-and-library structure. I'm fine with changing the default.

"Test suite" looks very weird. There should be a separate option to add a test component to the package description.

The way this is solved in other interactive UIs is that you answer the installation type at the start:

  • do whatever
  • simple installation
  • expert installation

I like this.

Notice that cargo init (as well as cargo new) doesn't have an interactive mode at all. This only supports my feeling that the default should be non-interactive.

I generally suggest to look into cargo init (new) more. E.g. I'd love to have the --vcs option that sets up a version control system for the new project.

the comments have to go.

The right thing to do here is a commented example in the cabal manual with links to the field definitions, and cabal init can output (perhaps only in interactive mode) a link to it.

agree.

ulysses4ever avatar Oct 02 '25 13:10 ulysses4ever

There should be a separate option to add a test component to the package description.

There is, it's just not shown until many options later.

Kleidukos avatar Oct 02 '25 13:10 Kleidukos

This only supports my feeling that the default should be non-interactive.

Yes. That should go for all cli tools.

Since we now have a cross platform vty/brick, you could also consider doing something along the lines of summoner. There's a lot of code in GHCup that you could copy paste as template (dialogs, selection menus, etc.) as well.

The current cabal interactive mode is not modern at all (typing in numbers at a prompt). But that's slightly tangential to this ticket.

I just want to call for caution about castrating the current interactive mode.

hasufell avatar Oct 03 '25 05:10 hasufell

The current cabal interactive mode is not modern at all

agreed, and thanks for bringing this up @hasufell! I think we could discuss it as a separate topic, though: it looks fairly orthogonal to the discussion of the things that cabal init produces. Modernization of the interactive UI could be a great GSOC project. Let me write it down in a discussion: https://github.com/haskell/cabal/discussions/11238#discussioncomment-14584731

ulysses4ever avatar Oct 03 '25 13:10 ulysses4ever

Thank you all for the feedback, I'll submit different PRs that address the most immediate issues, so that we can merge in incremental change and debate finer points without preventing unrelated changes.

Kleidukos avatar Oct 18 '25 16:10 Kleidukos