How am I meant to split code between src/Lib.hs and app/Main.hs in a new stack project?

This separation of modules into folders can be any way you want. The naive idea is that you put almost all logic into the Lib folder. Main.hs then just

  • imports required parts from Lib,
  • reads command-line arguments, and
  • runs stuff.

You can rename app into executables and change the corresponding lines in .cabal file. Actually, you can come up with an arbitrary file hierarchy.

In our company project, we use another but also very popular approach. And our file hierarchy looks like this:

.
|-- bench
|-- src
    |-- exec1
        |-- Main.hs
    |-- exec2
        |-- Main.hs
    |-- SuperCoolLibrary
        |-- LibModule1.hs
        |-- LibModule2.hs
|-- test
|-- Setup.hs

Other stack.yaml, .cabal, etc. files are not shown here.

Actually, if you are writing an application, you can just create one Main.hs file and put all logic inside the main function. You won't believe it but as a Haskell lecturer I saw such code from my students :( Though I don't suggest you write code that way.

If you are writing a library then you don't need Main.hs files and the main function at all. You can look at a simple example like this library (it allows you to automatically generate command-line options from data types): optparse-generic

I hope I helped clearing up your confusion.


The main reason it's typically set up like this even for an application is for writing tests. Say you create a default stack project called foo, the test suite foo-test will depend on the foo library, as will the foo-exe. If you were to put all your functions into app/Main.hs, then those functions cannot be tested from the foo-test test suite.

If you're just playing around and don't care about having a test suite, you could base your stack project on the simple template:

$ stack new foo simple

If you'd like to set up testing, I like tasty. You'd modify your .cabal file something like this:

test-suite foo-test
  type:                exitcode-stdio-1.0
  hs-source-dirs:      test
  main-is:             Spec.hs
  build-depends:       base
                     , foo
                     , tasty
                     , tasty-hunit
                     , tasty-quickcheck
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  default-language:    Haskell2010

Then take a look at the example.