Learn You a Haskell for Great Good! Modules

Author: Miran Lipovača. Link to original: http://learnyouahaskell.com/modules (English).
Tags: Haskell, Programming, tutorial, программирование, учебник, Хаскель Submitted by asinitsyn 03.11.2009. Public material.

Translations of this material:

into Russian: Изучай Haskell ради Добра! Модули. 99% translated in draft. Almost done, let's finish it!
Submitted for translation by asinitsyn 03.11.2009 Published 2 years, 3 months ago.

Text

Modules

Loading modules

A Haskell module is a collection of related functions, types and typeclasses. A Haskell program is a collection of modules where the main module loads up the other modules and then uses the functions defined in them to do something. Having code split up into several modules has quite a lot of advantages. If a module is generic enough, the functions it exports can be used in a multitude of different programs. If your own code is separated into self-contained modules which don't rely on each other too much (we also say they are loosely coupled), you can reuse them later on. It makes the whole deal of writing code more manageable by having it split into several parts, each of which has some sort of purpose.

The Haskell standard library is split into modules, each of them contains functions and types that are somehow related and serve some common purpose. There's a module for manipulating lists, a module for concurrent programming, a module for dealing with complex numbers, etc. All the functions, types and typeclasses that we've dealt with so far were part of the Prelude module, which is imported by default. In this chapter, we're going to examine a few useful modules and the functions that they have. But first, we're going to see how to import modules.

The syntax for importing modules in a Haskell script is import <module name>. This must be done before defining any functions, so imports are usually done at the top of the file. One script can, of course, import several modules. Just put each import statement into a separate line. Let's import the Data.List module, which has a bunch of useful functions for working with lists and use a function that it exports to create a function that tells us how many unique elements a list has.

import Data.List

numUniques :: (Eq a) => [a] -> Int

numUniques = length . nub

When you do import Data.List, all the functions that Data.List exports become available in the global namespace, meaning that you can call them from wherever in the script. nub is a function defined in Data.List that takes a list and weeds out duplicate elements. Composing length and nub by doing length . nub produces a function that's the equivalent of \xs -> length (nub xs).

You can also put the functions of modules into the global namespace when using GHCI. If you're in GHCI and you want to be able to call the functions exported by Data.List, do this:

ghci> :m + Data.List

If we want to load up the names from several modules inside GHCI, we don't have to do :m + several times, we can just load up several modules at once.

ghci> :m + Data.List Data.Map Data.Set

However, if you've loaded a script that already imports a module, you don't need to use :m + to get access to it.

If you just need a couple of functions from a module, you can selectively import just those functions. If we wanted to import only the nub and sort functions from Data.List, we'd do this:

import Data.List (nub, sort)

You can also choose to import all of the functions of a module except a few select ones. That's often useful when several modules export functions with the same name and you want to get rid of the offending ones. Say we already have our own function that's called nub and we want to import all the functions from Data.List except the nub function:

import Data.List hiding (nub)

Another way of dealing with name clashes is to do qualified imports. The Data.Map module, which offers a data structure for looking up values by key, exports a bunch of functions with the same name as Prelude functions, like filter or null. So when we import Data.Map and then call filter, Haskell won't know which function to use. Here's how we solve this:

import qualified Data.Map

This makes it so that if we want to reference Data.Map's filter function, we have to do Data.Map.filter, whereas just filter still refers to the normal filter we all know and love. But typing out Data.Map in front of every function from that module is kind of tedious. That's why we can rename the qualified import to something shorter:

import qualified Data.Map as M

Now, to reference Data.Map's filter function, we just use M.filter.

Use this handy reference to see which modules are in the standard library. A great way to pick up new Haskell knowledge is to just click through the standard library reference and explore the modules and their functions. You can also view the Haskell source code for each module. Reading the source code of some modules is a really good way to learn Haskell and get a solid feel for it.

To search for functions or to find out where they're located, use Hoogle. It's a really awesome Haskell search engine, you can search by name, module name or even type signature.

Data.List

The Data.List module is all about lists, obviously. It provides some very useful functions for dealing with them. We've already met some of its functions (like map and filter) because the Prelude module exports some functions from Data.List for convenience. You don't have to import Data.List via a qualified import because it doesn't clash with any Prelude names except for those that Prelude already steals from Data.List. Let's take a look at some of the functions that we haven't met before.

intersperse takes an element and a list and then puts that element in between each pair of elements in the list. Here's a demonstration:

ghci> intersperse '.' "MONKEY"

"M.O.N.K.E.Y"

ghci> intersperse 0 [1,2,3,4,5,6]

[1,0,2,0,3,0,4,0,5,0,6]

intercalate takes a list of lists and a list. It then inserts that list in between all those lists and then flattens the result.

ghci> intercalate " " ["hey","there","guys"]

"hey there guys"

ghci> intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]]

[1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]

transpose transposes a list of lists. If you look at a list of lists as a 2D matrix, the columns become the rows and vice versa.

ghci> transpose [[1,2,3],[4,5,6],[7,8,9]]

[[1,4,7],[2,5,8],[3,6,9]]

ghci> transpose ["hey","there","guys"]

["htg","ehu","yey","rs","e"]

Say we have the polynomials 3x2 + 5x + 9, 10x3 + 9 and 8x3 + 5x2 + x - 1 and we want to add them together. We can use the lists [0,3,5,9], [10,0,0,9] and [8,5,1,-1] to represent them in Haskell. Now, to add them, all we have to do is this:

ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]

[18,8,6,17]

When we transpose these three lists, the third powers are then in the first row, the second powers in the second one and so on. Mapping sum to that produces our desired result.

foldl' and foldl1' are stricter versions of their respective lazy incarnations. When using lazy folds on really big lists, you might often get a stack overflow error. The culprit for that is that due to the lazy nature of the folds, the accumulator value isn't actually updated as the folding happens. What actually happens is that the accumulator kind of makes a promise that it will compute its value when asked to actually produce the result (also called a thunk). That happens for every intermediate accumulator and all those thunks overflow your stack. The strict folds aren't lazy buggers and actually compute the intermediate values as they go along instead of filling up your stack with thunks. So if you ever get stack overflow errors when doing lazy folds, try switching to their strict versions.

concat flattens a list of lists into just a list of elements.

ghci> concat ["foo","bar","car"]

"foobarcar"

ghci> concat [[3,4,5],[2,3,4],[2,1,1]]

[3,4,5,2,3,4,2,1,1]

It will just remove one level of nesting. So if you want to completely flatten [[[2,3],[3,4,5],[2]],[[2,3],[3,4]]], which is a list of lists of lists, you have to concatenate it twice.

Doing concatMap is the same as first mapping a function to a list and then concatenating the list with concat.

ghci> concatMap (replicate 4) [1..3]

[1,1,1,1,2,2,2,2,3,3,3,3]

and takes a list of boolean values and returns True only if all the values in the list are True.

ghci> and $ map (>4) [5,6,7,8]

True

ghci> and $ map (==4) [4,4,4,3,4]

False

or is like and, only it returns True if any of the boolean values in a list is True.

ghci> or $ map (==4) [2,3,4,5,6,1]

True

ghci> or $ map (>4) [1,2,3]

False

any and all take a predicate and then check if any or all the elements in a list satisfy the predicate, respectively. Usually we use these two functions instead of mapping over a list and then doing and or or.

ghci> any (==4) [2,3,5,6,1,4]

True

ghci> all (>4) [6,9,10]

True

ghci> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"

False

ghci> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup"

True

iterate takes a function and a starting value. It applies the function to the starting value, then it applies that function to the result, then it applies the function to that result again, etc. It returns all the results in the form of an infinite list.

ghci> take 10 $ iterate (*2) 1

[1,2,4,8,16,32,64,128,256,512]

ghci> take 3 $ iterate (++ "haha") "haha"

["haha","hahahaha","hahahahahaha"]

splitAt takes a number and a list. It then splits the list at that many elements, returning the resulting two lists in a tuple.

ghci> splitAt 3 "heyman"

("hey","man")

ghci> splitAt 100 "heyman"

("heyman","")

ghci> splitAt (-3) "heyman"

("","heyman")

ghci> let (a,b) = splitAt 3 "foobar" in b ++ a

"barfoo"

takeWhile is a really useful little function. It takes elements from a list while the predicate holds and then when an element is encountered that doesn't satisfy the predicate, it's cut off. It turns out this is very useful.

ghci> takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]

[6,5,4]

ghci> takeWhile (/=' ') "This is a sentence"

"This"

Say we wanted to know the sum of all third powers that are under 10,000. We can't map (^3) to [1..], apply a filter and then try to sum that up because filtering an infinite list never finishes. You may know that all the elements here are ascending but Haskell doesn't. That's why we can do this:

ghci> sum $ takeWhile (<10000) $ map (^3) [1..]

53361

We apply (^3) to an infinite list and then once an element that's over 10,000 is encountered, the list is cut off. Now we can sum it up easily.

dropWhile is similar, only it drops all the elements while the predicate is true. Once predicate equates to False, it returns the rest of the list. An extremely useful and lovely function!

ghci> dropWhile (/=' ') "This is a sentence"

Pages: ← previous Ctrl next
1 2 3 4 5 6 7

© Miran Lipovača. License: creative commons attribution noncommercial blah blah blah ... license