Different behavior of cabal repl for library vs. executable - haskell

Using cabal repl seems to do nothing at all when used on library projects, but works fine for executable projects. Is this expected behavior that I just don't understand?
If I have a file containing simply
go = putStrLn "test"
and use cabal init with all the defaults (but choose "library" as the type), then running cabal repl just produces the some text about configuring and preprocessing the library and never enters a REPL environment. The exact same steps, but with "executable" selected as the type, puts me right into GHCi as expected.
The code works fine when loaded directly into GHCi.

For cabal repl to load your modules, you have to first name them in code and then specify them in your project's .cabal file as exposed:
-- MyModule.hs
module MyModule where
go = putStrLn "test"
-- MyProject.cabal
name: MyProject
-- other info ...
library
exposed-modules: MyModule
-- other options ...
Then when you run cabal repl, it'll have access to everything in your sandbox (if present) and the exposed modules. It might also work if you specify them as other-modules instead of exposed-modules, but I haven't tried that one out.

Related

How to configure .ghci file to import all loaded modules

let say I have a project which is just a bunch of Haskell modules with exercises. I'd like to provide a .ghci which automatically imports all modules into ghci scope. The problem is, I can not run import nor :m +Module.Name within .ghci file.
Clearly cabal repl is reading the .ghci file because options like the prompt are readed. Also it loads modules correctly, but it doesn't bring them into scope (only one gets imported). If I try to add import OtherModule to .ghci file, then I get the error
module is member of hidden package fail-ghci
Perhaps you need to add ‘fail-ghci’ to the build-depends in your .cabal file.
But I can't add fail-ghci to the cabal file, because the library can't depend on itself!!
To reproduce. Create a simple cabal project. For example:
src
|- Module1.hs # include dummy function func1 :: Int
|- Module2.hs # include dummy function func2 :: Int
fail-ghci.cabal
.ghci
The content of fail-ghci.cabal is
name: fail-ghci
version: 0.1.0.0
license: BSD3
license-file: LICENSE
build-type: Simple
library
exposed-modules:
Module1
, Module2
hs-source-dirs:
src
build-depends:
base >=4.7 && <5
default-language: Haskell2010
If you set .ghci as
:set prompt "> " -- Set this option to ensure .ghci is readed by cabal repl
It will work fine and will bring Module1.hs into scope, so func1 is available. But Module2.hs wont be in the scope, If I want to use it I'd need to execute import Module2 or equivalent.
Now, I'd like this to happen automatically when running cabal repl because my project has many modules. The obvious (to me) choice is to modify .ghci as
:set prompt "> " -- Set this option to ensure .ghci is readed by cabal repl
import Module2 -- Equivalent :m +Module2
But the ghci is unable to import the module, despite of that command woring correctly within ghci. What's the correct configuration to do this?
One workaround I like is to add a new module to your project, perhaps called DefaultRepl or similar, that imports everything you want. If you put this at the top of your exposed-modules list, then it will be loaded up when you run cabal repl and bring everything that it imports into scope.
I suppose the problem is simply that cabal executes the commands in your .ghci file before it loads the current project.
Indeed when I cabal repl in a similar project, the startup output is:
Build profile: -w ghc-9.0.2 -O1
In order, the following will be built (use -v for more details):
- fail-ghci-0.1.0.0 (first run)
Preprocessing library for fail-ghci-0.1.0.0..
GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from /tmp/scratch/.ghci
[1 of 2] Compiling Module1 ( src/Module1.hs, interpreted )
[2 of 2] Compiling Module2 ( src/Module2.hs, interpreted )
Ok, two modules loaded.
ghci>
My guess would be that this is probably because cabal repl starts a ghci session (which loads the .ghci file during startup) and then uses its API to load the project environment. (I was able to confirm that a .ghci file can import modules from packages that are fully installed, rather than from a current project being cabal repld)
I don't know of any way to automatically execute commands after the project has been loaded. But you can have your .ghci file define a shortcut command that will import all the modules you want. That way you have at least have a single quick command to execute once you're in the ghci shell, instead of manually importing everything. Something like this:
:def loadall (const . pure) ":m + *Module1 *Module2"
-- or
:def loadall (const . pure) "import Module1 Module2"
-- or etc
Then you can just enter :loadall at the ghci prompt to get your modules imported.

Cabal update now can't load any modules from "hidden packages"

I've been working on a project and recently I did a cabal update.
I usually roll into ghci like:
$ ghci -package-db ~/.cabal/store/ghc-8.10.7/package.db
After the update loading module in my project results in even the basic Haskell modules like System.Random or MonadIO fails with the following errors when trying to load my own module called ProcessIO:
ProcessIO.hs:50:1: error:
Could not load module ‘Data.IORef.MonadIO’
It is a member of the hidden package ‘monadIO-0.11.1.0’.
You can run ‘:set -package monadIO’ to expose it.
(Note: this unloads all the modules in the current scope.)
Locations searched:
Data/IORef/MonadIO.hs
Data/IORef/MonadIO.lhs
Data/IORef/MonadIO.hsig
Data/IORef/MonadIO.lhsig
I checked that maybe the .cabal file build-depends versions might have been altered, but the cabal package.db directory contains all the right versions of the dependencies in the .cabal file. For example the error above complains abot monadIO-0.11.1.0 being hidden however: in package.db/ we see the right version exists:
monadIO-0.11.1.0-0aec75273f3fef94783e211a1933f8ac923485a963be3b6a61995d4a88dd1135.conf
I should say I haven't looked at the package.db files before because everything simple worked so there may be something telling about the .conf file name that signals something is wrong.
Either way, can't build anything and I need some help!
EDIT: posting my default environments file ~/.ghc/x86_64-linux-8.10.7/environments/default in case it matters:
clear-package-db
global-package-db
package-db /home/surya/.cabal/store/ghc-8.10.7/package.db
package-id ghc-8.10.7
package-id bytestring-0.10.12.0
...
(Let me know if I need to share more of it... or less)

Duplicate packages in Haskell environment?

I put this in a file (see bottom) and got the following error when loading it in ghci (i.e. start ghci then :l file):
7:13: error:
• Couldn't match expected type ‘network-uri-2.6.1.0#network-uri-2.6.1.0-7BN1tbB3iHQ2XgvmqLAYph:Network.URI.URI’
with actual type ‘Network.URI.URI’
NB: ‘Network.URI.URI’
is defined in ‘Network.URI’
in package ‘network-uri-2.6.1.0#network-uri-2.6.1.0-80FpvaNUTSDFCPv0sSze40’
‘network-uri-2.6.1.0#network-uri-2.6.1.0-7BN1tbB3iHQ2XgvmqLAYph:Network.URI.URI’
is defined in ‘Network.URI’
in package ‘network-uri-2.6.1.0#network-uri-2.6.1.0-7BN1tbB3iHQ2XgvmqLAYph’
Have I somehow installed two slightly different versions of network-uri, or what is this? How do I resolve it?
I've hardly used cabal but tried cabal install --reinstall network-uri, went fine but problem persists.
File contents:
import Network.HTTP
import Network.URI (parseURI)
import Data.Maybe (fromJust)
myRequestURL = "http://www.virginia.edu/cgi-local/ldapweb"
myRequest :: String -> Request_String
myRequest query = Request {
rqURI = fromJust $ parseURI myRequestURL
, rqMethod = POST
, rqHeaders = [ mkHeader HdrContentType "text/html"
, mkHeader HdrContentLength $ show $ length body ]
, rqBody = body
}
where body = "whitepages=" ++ query
main :: IO ()
main = do
response <- simpleHTTP $ myRequest "poon"
putStrLn ""
EDIT Re-installed haskell-platform. After this, ghc-pkg list reports all packages to be under (?) /usr/lib/ghc/package.conf.d, and the program I tried to copy loads just fine.
EDIT 2 After running cabal install some-pkg, ghc-pkg list reports that the newly installed stuff is in another directory. Indeed, ~/.cabal/config specifies install dirs for user, as well as install dirs for global. How is all this supposed to be managed properly?
GHC understands the concept of package databases. Cabal manages multiple package databases. There's a system package database, and a user package database. When you compile something with Cabal V1, it uses the union of these package databases. This is a bit of a time bomb, because, as you see, GHC does not like it when you have multiple of the same package. When you issued cabal install network-uri, cabal decided to install one copy of that package into your user database, though you already had one in the system database. When you tried to use ghc, it picked up on the two copies and got confused. Because cabal does not support removing packages, your Haskell installation was pretty much borked, and you reinstalled. I believe you could have done a more conservative rm ~/.ghc, or, had you been really adventurous, used the low-level ghc-pkg command to edit the package database.
Now that you've reinstalled the Platform, I would heavily recommend never using cabal install again. It's old, it's broken, cabal itself tells you not to use it. If you're using the Platform, I think it has stack in it, which is a different build manager, based on Cabal, which is not prone to this kind of breakage. Or, you can just stick to the V2 series of cabal commands, which is what I prefer.
Cabal V2 does not directly support your usecase, where you install a package globally and compile a program against it. This is because doing so is fundamentally broken: global package databases really just don't work for GHC. What Cabal V2 does is install packages into your home directory, ~/.cabal/store. You then have to explicitly tell it which packages you want, and it will construct a package database that contains each required package, without any wonkiness. You can get a REPL with a certain set of packages by issuing (this will take you to a temporary directory; you may need to :cd yourself back.)
cabal v2-repl -b network-uri -b package2 -b etc
If you want to actually compile files with ghc with a set packages, you have to actually define a package with a .cabal file. Here's a stub .cabal file:
name: temporary-pkg
version: 0.0.0.0
build-type: Simple
cabal-version: >=2.0
executable main -- if there is no Main, say "library" instead of "executable <name>"
default-language: Haskell2010
main-is: Main.hs -- only valid in executable stanzas
hs-source-dirs: src -- source files go here
build-depends: base, network-uri, package2, etc -- packages go here
Create and enter a directory, place that in temporary-pkg.cabal, place your sources in src/, and you can issue stuff like cabal v2-build and cabal v2-repl and cabal v2-exec main, etc. cabal will go off and install dependencies from Hackage into ~/.cabal/store as needed. cabal v2-install is therefore not that useful here. In general, v2-installing libraries is unnecessary, but v2-installing packages with executables is useful. Again, for hygiene reasons, you have to list all the packages you are importing from, but you don't need to list their dependencies or anything insane like that.
Cabal V2 still doesn't support uninstalling packages, though the point is that should not be necessary. However, if you find ~/.cabal/store getting too large for your liking, you can just nuke it, and should be built back up as packages are requested.

Typechecking multiple 'Main's

I have a Haskell library with several executables (tests, benchmarks, etc), in total about six. When I do some refactoring in the library, I usually need to make some small change to each of the executables.
In my current workflow, I separately compile each executable (say, with GHCi) and fix each one up. This is tedious because I have to type out the path to each executable, and moreover have to reload all of the (very large) library, which even with GHCi takes some time.
My first thought to solve this issue was to create a single dummy module that imports the executable "Main" modules. However, this (of course) requires that the "Main" modules have a module name like module Executable1 where .... But now cabal complains when compiling the executable that it can't find a module called "Main" (despite explicitly listing "main-is" in the cabal file for each executable.)
I also tried ghci Exec1.hs Exec2.hs ..., but it complains module ‘main#main:Main’ is defined in multiple files.
Is there an easy way to load multiple "Main" modules at once with GHCi so I can typecheck them simultaneously?
Cabal’s main-is option only tells Cabal what filename it should pass to GHC. Cabal does not care about it’s module name.
GHC itself has a flag, also called -main-is, documented here which tells the compiler what module conains the main function.
So this works:
executable foo
main-is: Foo.hs
ghc-options: -main-is Foo
Of course Foo.hs should start with module Foo where… and export main. As usual, the module name and file name needs to match.
This way, all executable can have different module names and you can load them all in GHCi.
If you also want to change the name of the main function, write ghc-options: -main-is Foo.fooMain. I would guess you could even have all executables have the same module but different main-functions this way.

Minimal cabal file for use in sandbox

I’m trying to write a project with the Hakyll library. In order to avoid messing up with my system, I’d install it in a cabal sandbox in the same folder where my Hakyll project lives.
Being more or less a beginner, I’m still struggling with getting the pest practices right. A simple approach would be to just do
$ cabal sandbox init
$ cabal install hakyll
$ cabal exec ghc -- --make site.hs
where the last line compiles my Hakyll generator using the libraries in the sandbox. The obvious disadvantage is that this is not reproducible. The main version of Hakyll could have change when I try to run it again from a clean checkout.
Another approach would be to write a proper project.cabal file (For example like this: chromaticleaves.cabal) and then do cabal install or cabal run.
However, I feel that this may be a bit too much information. As I do not intend to publish this project any more than needed, I’m not really convinced I need to put a project name and version number in there. (For example, in a Ruby Gemfile, I would also only specify the libraries and nothing else unless I wanted to publish a gem myself.)
So, eventually I figured that with a file like
$ cat project.cabal
cabal-version: >= 1.2
library
build-depends: base >=4.6
, containers
, process
, hakyll >=4.5
, pandoc
, pandoc-types
I can type
$ cabal sandbox init
$ cabal install --only-dependencies
$ cabal exec ghc -- --make site.hs
and it seems to download all dependencies and is able to compile the file.
Is this a reasonable approach or is the best practice really to give a full specification with name, version and executable sections in the cabal file?
Edit: Apparently, my approach does not let me do cabal repl. So either there exists a completely different way of doing it or it seems I have to go with a fuller specification.
I use your first approach myself for my Hakyll-based webpage. You don't need to create a .cabal file to pin the Hakyll version, you only need to add the following line to cabal.config:
constraints: hakyll == 4.5
I think that cabal repl will work with this approach, but you will need to load site.hs manually (:l site.hs). Or you can use cabal exec ghci -- site.hs.

Resources