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.