Support Mod.hs files?

🤷 Call it personal preference.

This concept is just pretty widespread in other languages, and I miss it every so often in Haskell, and was wondering if anyone else feels similarly. Seems like probably not

1 Like

For what it’s worth, I do. I doubt the Haskell community will want to flex on this for such a minor benefit as ‘convenience of people clicking around unfamiliar projects’, but I am also inconvenienced when I’m rifling through SomeProject/Frobber/ searching for the reason something is being frobbed in an unexpected way, only to find that I needed to jump up a level to read SomeProject/Frobber.hs.

1 Like

…like the old WWW custom of having an index.html file in directories so that:

is expanded to


Agreed - it’s their problem, so let them solve it (or provide a patch). We already have enough “conveniences” to quarrel over e.g:

> chdir that/path/to/SomeProject/Frobber

> view ../Frobber.hs


Correct. As I said, an inconvenience, not a blocker.

This simply doesn’t match how directories are used most of the time, outside of module systems that resemble Haskell’s. If I told you I have a taxes directory, with files taxes/2020.pdf, taxes/2021.pdf, taxes/2022.pdf, etc., where would you expect to find a text file that contains data spanning multiple years? In taxes/all-years.txt? Or taxes.txt alongside the taxes directory? If I told you that system service vorpal writes error logs to /var/log/vorpal/error.log, where would you be more surprised to see non-error, normal operation logs? /var/log/vorpal/info.log, or /var/log/vorpal.log?

Tools like ripgrep will automatically search for their needles recursively in the directory you are in, or in the directory you specify. They don’t reach out to the parent directory, look for a file whose base name matches your current directory, and search there too. Why would they?

So if I’m in SomeProject/Frobber, of course I can manually run rg "some text" . ../Frobber.hs, if I remember to. I can also spend five minutes writing a shell function that I train myself to use in place of rg that does this for me. I can do any number of things to work around this minor inconvenience. But the OP thought he might be alone in experiencing it, and he isn’t.



  • if AnotherProject/Blagger/Mod.hs, already exists, what happens then?

  • If Mod.hs is the only file in SomeOtherProject/Grebber, should:


    be considered “the same as” :



My idea was to only fallback to Foo/Bar/Mod.hs if Foo/Bar.hs does not exist. And one could still explicitly import Foo.Bar.Mod if they wish

And yes, Mod.hs works independent of any other file. Literally the only change would be changing the behavior from

let f = replace "." "/" importMod <> ".hs"


let f' = replace "." "/" importMod
f <- bool (f' <> "/Mod.hs") (f' <> ".hs") <$> doesFileExist (f' <> ".hs")

Making a toolchain chain to appease a MS-funded tool is totally silly. Talk about putting the horse in the cart and giving it the reins lmao.


…and no doubt it only took a few patches/s for do notation to “go viral” too. Moreover, this to me looks far too similar to how Subversion worked back in “its heyday” - one .svn sub-directory for each directory of a codebase it managed. So it’s bemusing to be told that other presumably-newer languages are now (to an extent) following Subversion in that way.

I don’t personally see the benefit of this specific change, but I do like the idea of being able to separate the concern of “where the file is on a filesystem” from the concern of “what Haskell module does this file contain”. I suspect that belongs in a build too though, not GHC. (Maybe some tool already has it. I don’t know.)

Where have I encountered this idea before…that’s right, it was here:

Sigh … I also still (still!) get tripped up by logically related modules getting forced into different directories, and would probably use something like Mod.hs if it existed.

That said, there is a certain simple usefulness in modules corresponding 1-1 with the directory tree, and the proposal is a change that’s too disruptive to the ecosystem to have legs.

On the upside, no tooling support is required if you simply stop using Foo directly and only use Foo.Mod :slight_smile: I might try something like that some time…


Sigh … I also still (still!) get tripped up by logically related modules getting forced into different directories, and would probably use something like Mod.hs if it existed.

If they’re that “logically related”, could you just move such modules to their own (sub)directory?

[…] no tooling support is required if you simply stop using Foo directly and only use Foo.Mod […]

…but will you be happy with staring at the ".Mod" suffix on every third, fourth or fifth import everywhere? For a while, Rust had a reserved word (but for a different purpose) and it was eventually removed due to overuse, to the point of being irrelevant:

Alternatively, how many here thought -XBlockArgument “seemed reasonable at the time” ?

I’m confused. Your first two paragraphs seem to contradict each other, or at least lead directly to the solution which you’ve been arguing against. That is, tooling support for having logically related modules in the same directory, without requiring one of them to have a suffix in the module name.

Maybe that Mozilla link would make things clearer, but it’s broken.

Also, I continue to think BlockArguments is reasonable, and don’t seriously expect anyone to abuse it as in the thread you linked. So I’d say any parallels are a stretch.

Like @chreekat, I’m sympathetic to this idea, and would probably use it if it became standard, but I’m not sure it’s worth the hassle.

I might be completely and utterly wrong here, but isn’t this achievable if we break the whole compilation unit = file = module thing, specifically the file = module part?

I imagine if you had the file Foo/Mod.hs and then Mod.hs looks like this

module Foo where

Would that achieve what OP is asking for?

Not to imply at all that it’s so easy to do or anything, just asking theoretically

…because they addressed two separate points:

  • The first one posed an alternative solution: a new directory to hold those “logically-related” modules, to avoid them “getting forced into different directories”.

  • The second one was speculative: if per-directory Mod modules were used everywhere (much like what some seem to be doing with -XBlockArgument) would we be happy with that, or would constantly seeing all those Mod suffixes be irritating?

As for the now-broken URL:

…look for the response starting with:

“It shouldn’t be too complex” is sadly optimistic […]

GHC also supports naming your file Foo.Bar.Mu.hs and skipping the directory structure, which I prefer, but a large amount of tooling assumes the Foo/Bar/Mu.hs convention. So in practice if you want people to enjoy collaborating with you there isn’t a choice.


I’ve read that before, but that doesn’t work for me with GHC 9.4.6:

$ cat Foo.Bar.Mu.hs 
module Foo.Bar.Mu where

x :: Int
x = 10

$ cat Test.hs
import Foo.Bar.Mu
main = print x

$ runhaskell Test.hs

Test.hs:1:1: error:
    Could not find module ‘Foo.Bar.Mu’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
1 | import Foo.Bar.Mu
  | ^^^^^^^^^^^^^^^^^

I see; it’s been some years since I encountered this, so it may have worked accidentally, on an older version, or by some unusual interaction with Cabal or Stack. :+1:

To me this looks like Python’s files which can be used to control exports from a “package” (directory). But afaik, Python is moving away from this pattern.

1 Like