Episode: 2958
Title: HPR2958: Haskell modules
Source: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr2958/hpr2958.mp3
Transcribed: 2025-10-24 13:52:33

---

This is HPR episode 2958, for Wednesday 4th of December 2019. Today's show is entitled
Haskell Modules, and it's part of the series Haskell, and it's hosted by Tukutoruto,
and it's about 23 minutes long. It carries a clean flag. The summary is Tukutoruto talks
about Haskell Modules. This episode of HPR is brought to you by Ananasthos.com.
Get 15% discount on all shared hosting with the offer code HPR15. That's HPR15.
Better web hosting that's honest and fair at Ananasthos.com.
Hello, this is Tukutoruto, and you are listening to Hack about the great deal. This time I'm going to talk
you about Haskell Modules. So, when you have a small program, it's easy to have all the code
in a single file, and there's no problems, but as the program grows, you often feel like you have
to have a, you need a way to organize your code a little bit better. So, grouping things together
that relate to each other and making sure that if there's two concepts that have the same name,
they don't collide. If you have all the code in a single file, all the identifiers have to be
unique. You cannot have two things named similarly. And the third thing is when you have all the
code in a single file, when you change any part of that file, the Haskell will compile whole file
and with a big program that will take a while. So, if you and for that Haskell has modules. So,
these are way to group related things together, help with the name collisions and speed of the
compilation. If your big program has been broken into modules, first time you compile it,
the whole program is compiled, but after that only the module that you change will be compiled
and all the modules that depend on that. And of course, that chain continues. So, everything that
depends directly or indirectly to the thing that you change will get compiled.
And there's two parts in this. This one first one is how to make modules. And second part is
how to use the modules. And I'm going to talk about both of them this time.
Modules are declared. Each module lives in a separate file and they can be in a
hierarchy of a folder on a hard drive. So, each module is in a separate file and they can have a
hierarchy. So, for example, you have a module name data, data. Maybe. Oh, you can have a data.
array.accelerate.cublas.level2.patched. So, those can be really long.
We have a made-of example in this episode called Multiplexer that resides in a multiple
multiplexer of HS that are module. And in top of that file, we have a module declaration. We've
just write module, multiplexer, open parent, mix, match, plexer, scooper.close parent there,
and then the implementation. I'm omitting the full function and type definitions because
they aren't. I haven't present them and they are not important. We just present that we have two
functions called mix and match and two types called plexer and scooper. And in a module definition,
plexer doesn't have a dots after it. Inside parent, that means that only the pipe is exported.
And scooper has dots. That means that if a scooper is a folder-priced data type, then all the
value constructors are exported. And if it's a record, then all the field accessors are exported.
So, that list after the module name is the export list. That defines one of those things that
are defined inside of the module available to the outside of the module. There would be a lot
of other code, but that's not visible to the outside world. Okay, so if you leave that list
of that version, that export list, that causes everything to be available to the outside world.
I like defining explicitly what is available to the outside world.
Okay, then you are not, we have a, that our multiplexer module written and tested and everything,
we want to use it. So there's a, I was actually surprised when I was reading on the modules,
how many different ways they are to import them. Importing means that you are taking a module
in the use. We have a, we have a, for example, our programs main, old, resizing the main
of Hs file. And there we want to use this multiplexer thing is. And how you use them is to use
import function. That says the, I think I haven't talked about those before. Anyway, import is a
way of referring to a different module and bringing all the identifiers,
or some of the identifiers from there into the current module so that you can use them.
So the first, first, the easiest way is to use
just a import, we can just write in top of our file import multiplexer. And that brings all
those exported identifiers into our current module. All it brings qualified and unqualified names.
So in our example, we can then call that our mix function as a mix and parameters,
or we can call it multiplexer.mix and parameters. So there are two names for it now. One is the
unqualified name, that is just a name of the function. And the qualified name is the name of the
function, propended with the module name. Because sometimes they might be that,
I got it in a bit. My, my, you want to use qualified names. And this, like I said,
this imports everything. And sometimes you don't want to import everything for, for module
freezums. So to, easy, easy way is to explicitly define what you want to import.
You can just write import multiplexer, open barren, and then list of the, list of the
identifiers that you want to import. So if you want to import those four things, we will
just write import multiplexer, open barren, mix, match, lecture, scooper dot dot. So that explicitly
declares that I want to import multiplexer from multiplexer module mix and match functions,
lecture and scooper, pipes, and, and wall workers, fructors, or field accessors for the, for the scooper.
And if I didn't want to import anything, I could leave at least empty, then only type instances
would be, sorry, in type class. Then only instances of type classes would be imported.
Sometimes that's useful. And again, these imports are both qualified and unqualified names.
And if we didn't need the flexor and scooper, we could just write import multiplexer, open barren,
mix and match and closer. After that, only mix and match are available flexor and scooper aren't.
And if there's a, there could be a, there could be a two files or two modules that we are using.
And they both define the same, same identifier, for example, we want to be some completely related,
unrelated module that also would define mix. Then if we import that and the multiplexer
mix from both of those, then we don't, then the compiler can't understand what to, which, which
mix we are referring to. So, for those cases, we can use qualified names. We can call it with a
mix, we can call it multiplexer.mix and then the compiler knows which we are using, which we
want to use. And we can also make sure that we can also force using of the qualified names.
We can use import qualified multiplexer. That means that it imports everything from the
multiplexer module into our module, but it only imports qualified names. So, there isn't
mix anymore. There's only multiplexer and mix that we can use. And after that, they won't be name
collisions. And sometimes you are using that function quite a lot and providing multiplexer
in front of it. The module name gets tedious. So, you can rename the module, not by changing it
on the hot, not by changing the actual module file, but the, but the, you can import
a green image with that as a key word. So, you can say import multiplexer as m. After that,
you, the module is referred as m in your current, current module. So, you can, instead of writing
multiplexer, the mix of multiplexer.match to just write m.match. This, this is I'm using
quite a lot. And you can have multiple, multiple imports from the same module, like you can,
you could write import qualified multiplexer as m, mix and import multiplexer hiding mix.
That means that your third one is importing only the mix function, but I've got qualified
with a qualified name and multiple black set and renamed as m. So, you can only call m.mix and
nothing else available. And then you are importing everything, both qualified and unqualified name
from the multiplexer without renaming, but you are hiding mix. So, everything else,
but the mix is now imported. And after that, you can call m.mix.match.multiplexer.match.multiplexer.multiplexer
scooper multiplexer scooper. Sometimes this is useful, I don't really often use that. So, hiding
another way, you can import everything except some defined list. So, that should now be all.
And all different ways of importing. And you can combine this. So, you can write import multiplexer
to import everything, both qualified and unqualified. You can import multiplexer open
brands, closed brands. And this time, you are importing only type class instances. So, for
example, if your type is defined to have a diameter of the instance of num class, then you are
only importing those instances. You can write import multiplexer open brand list of things to import,
that imports both qualified and unqualified. And the same thing, import qualified multiplexer
everything, but only qualified names. Import qualified multiplexer list of things to import.
And import multiplexer hiding list of what you are hiding. Everything else, but this list
that I am giving you. And this import both qualified and unqualified, import qualified multiplexer
hiding list of things to hide. Import multiplexer as n, you are renaming the multiplexer to
bm, both qualified and unqualified names, everything that is exported is given to you. Import multiplexer
as n, list of things to import. These are only these things defined in the list are imported,
both qualified and unqualified, but the modules renamed to n, n, import qualified multiplexer as n,
and import qualified multiplexer as n, list of things to import. I think I might have missed
some thing there. Yeah, no, that should be all. So you can mix and match this,
how you import things and you have a good control on how you want to import things.
So in short, some identifiers can be chosen to be imported while leaving others unimported.
Modules can be imported, qualified, that means that you are forcing an obligatory names
qualifier to import the identifiers. Some identifiers can be skipped with the hiding clause
and module namespaces can be renamed with the as clause.
There's a special module called prelude. This is a sort of base module that is automatically
imported. It contains lots of helpful pipes and functions, but sometimes you don't want to
want that. For example, they might be that you have written an own version of some functions
or you are using some other versions, written by some other person of those functions that are
defined in prelude, so you don't want to, you don't want to use that. For example, the
head function that is defined in the prelude gives you the first item of the list,
but if you call that function, if you apply that function with the empty list,
then you get a runtime error. I don't like runtime errors in Haskell, I try to
avoid those as much as possible. There's a different implementation for the head function
that instead of the first item, it returns maybe the first item, so if you give it a list of
something and that there's elements, it returns just that first element, and if that list
is empty, then it returns nothing. So it returns you a value, always, it doesn't crash.
So instead of the prelude version, I like to use that one. So there's a couple of ways of
not important, for not importing the prelude, you can write a pragma at the start of the,
at the start of the your file, these are compiler directives. So this is a, I don't
remember what those viscous looking parents, well now they are called viscous parents,
they are not the square brackets, they are not the parents, but they are the right. So open,
open up that, that has language, no implicit prelude, has error, has, dash, and close.
That's funny parenting, and this instructs the compiler not to implicit, not to import
prelude. You can also import prelude manually, because that turns off the automatic import,
and what you do is you can try to import qualified prelude at p, that means that only
only are qualified names are imported from the prelude, and now if you're calling head,
you are using your own version, and if you want to use the
version defined in the prelude, you use p.head. So, prelude is useful, it's really useful,
but there are some functions that I like to be implemented slightly differently.
Socular references, Hardcore doesn't support circular references, this is if you have two modules
that import each other either directly, or indirectly, that does not combine, their
compiler just refuses to compile that, so imports have to form a direct that as you click
graph, so they can refer to things and those things can refer to some other things, but nobody
can ever refer back to the first thing forming a loop. Of course, you can have some completely
different module that is not imported from anywhere, to refer to the first thing, not the first thing,
but anyway, you cannot have a circular imports. Sometimes these arise, and you're writing code,
and that means that you have to move code around, it might be that it is possible to move things
within existing modules, to create this circular reference, or sometimes you have to create a new
module and move something there, like recently I was working with space ships and people and
crews, and I think it was that people and crew were defining the same module, and then
somebody can, some people can own a space ship, but then space ship can have a crew and crew
is consists of people, so there was a circular reference that I prog up by introducing a third
module that I moved the crew related things, because then I could have people who own a space ship,
but space ship is interpreted, referring anyway else because crew is different,
separate module, the crew and space ship are sort of separate things then, but sometimes you just
have to move around code a little bit, sometimes it's easier and sometimes it just takes a
quite a bit of time to figure out how to do things, but at least a husker isn't as strict as a F sharp,
which is, in where you cannot use anything that is defined after the points they are using it,
so in F sharp you cannot have circular references, and you cannot, in a inside of a file you
can refer forward, you can only refer the backwards things that have been defined, which is kind of funny,
so that's about it, and compilation I mentioned already, so when you compile a big program,
everything is compiled that might take a time, and after that when you change one module,
only that module and everything dependent on that data crew and indirectly gets compiled,
so the compilation speed is, compilation is state up, and sometimes you end up with a sort of a,
for example, common or missed, some module that is compensating some common things that are used
everywhere in your program, and when you change anything in that module it records the compilation,
the compilation of most of your program that takes a time, so it of course is, of course if it's
a commanding, then it's a commanding and then it's used in everywhere, but I try to keep those
things that go in some very common module to the minimum and break up things as much as it
makes sense so that I don't have to change a module and then wait for the whole system to recompile,
that's it, now we know how to define modules and how to use them, and I don't have anything else
to talk about this this time, so questions, comments and feedback is always welcomed,
even data it is if you record your own HackerPublic Radio episodes,
and the test rate reached me, now there is either email or in the status where I'm
to do it or administer it on social, happy hacking!
You've been listening to HackerPublicRadio at HackerPublicRadio.org,
we are a community podcast network that releases shows every weekday, Monday through Friday,
today's show, like all our shows, was contributed by an HBR listener like yourself,
if you ever thought of recording a podcast and click on our contributing to find out how easy it
really is, HackerPublicRadio was founded by the digital dog pound and the infonomicum computer club
and is part of the binary revolution at binwreff.com, if you have comments on today's show,
please email the host directly, leave a comment on the website or record a follow-up episode yourself,
unless otherwise status, today's show is released on the creative comments,
attribution, share a light, 3.0 license.
