Support Mod.hs files?


Could you perhaps flesh out the motivation?

I think it’d be nice to have the option to keep module files colocated in the same place, in general

I don’t understand if that’s a motivation of the proposal or a description of the status quo. I suppose I don’t understand it because I don’t understand what “keep module files colocated in the same place” means.

it’s also a minor annoyance when browsing GitHub, and GitHub puts all directories above files, so you have to scroll up and down when navigating between X/Y.hs and X/Y/Z.hs.

I don’t find it particularly convincing that we need to introduce a new module location scheme in onder to avoid minor annoyances with the Github file viewer!

1 Like

I’d imagine the pushback will be “you can just re-export all modules in a subdir - this is a known idiom.” But maybe the idiom popular enough that it merits some automation?

Can this be first implemented as a plugin?

You can do that without a plugin:


@tomjaguarpaw sure I can flesh out the proposal more later. But to answer your question, by “colocated”, I mean in the same directory, e.g.

-- current

-- proposal, would act the same as current

Yes, the GitHub comment is certainly not enough to drive the whole change, it’s just a bonus to the overall proposal. I had assumed people have experience with Rust or Typescript, which use this idiom extensively, but perhaps I shouldn’t assume that. Here’s a more concrete reason:

I like to have my tests match the same module structure as my source code. If I want to test a module and a submodule, I have to do


It’s a little awkward that Foo/BarTest.hs is testing a submodule under the module FooTest.hs is testing.

The proposal would allow


Which makes it much clearer that ModTest.hs and BarTest.hs are testing behavior in the same area. The module names will be a bit different (Foo/Mod.hs => Foo, Foo/ModTest.hs => Foo.ModTest), but I’m willing to live with that.

@Ambrose that’s a bit different from what I’m proposing. I don’t want Mod.hs to reexport everything in all submodules. The same way you might implement some stuff in Foo.Bar and break out some helpers into Foo.Bar.Baz, I want the same thing, except putting the file containing Foo.Bar in the same directory as Foo.Bar.Baz.

@taylorfausak yes, that preprocessor does what @Ambrose suggested, but not what I’m asking for. I want to change the compiler to automatically find Foo/Bar/Mod.hs for an import Foo.Bar when Foo/Bar.hs doesnt exist.

1 Like

Is it? Why?

🤷 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.