- MCP server with stdio transport for local use - Search episodes, transcripts, hosts, and series - 4,511 episodes with metadata and transcripts - Data loader with in-memory JSON storage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
191 lines
16 KiB
Plaintext
191 lines
16 KiB
Plaintext
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.
|