Friday, October 16, 2009

Lists, ranges, and tuples

Okay, so you want to store multiple values in one variable. (Yes, you do. Don't question it.) So, of course, you use an array. In Haskell, you have two choices: you can use a list or a tuple.

Lists

A list is essentially what any non-functional language would call an array. It's an ordered set of values of the same type. List literals are set off by brackets, like so:
[1,2,4]


Checking the type tells you this:
> :t [1,2,4]
[1,2,4] :: (Num t) => [t]


Recall from the last post that, without any context, numbers are just some kind of number. [t] is Haskell's notation for a list of things of type t. Let's play around a bit more.

> :t ['D', 'e', 'a', 'd']
['D', 'e', 'a', 'd'] :: [Char]
> :t "prosthetic forehead"
"prosthetic forehead" :: [Char]

Remember that in Haskell a string is nothing more than a list of characters.

And, of course, we can nest lists for crazy fun times.
> :t [[1,2,3],[4,5,6]]
[[1,2,3],[4,5,6]] :: (Num t) => [[t]]


We've also got a few list functions to play with. Like these:

List concatenation:
> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]


The cons operator:
> 1:[2,4,8]
[1,2,4,8]

The cons operator takes a thing and a list of other things of the same type, and sticks the first thing on the front of the list. It's fast.

Obviously, you might want to access individual values in a list. You can do that, too!
> "rock to wind a piece of string around" !! 8
'w'

The indices are zero-based.

> head "Istanbul"
'I'
> tail "Istanbul"
"stanbul"
> init "Istanbul"
"Istanbu"
> last "Istanbul"
'l'

Those four functions return the first element, everything except the first element, everything except the last element, and the last element, respectively. Don't use these on empty lists.
> head []
*** Exception: Prelude.head: empty list

Yeah, Haskell doesn't like that.

These next functions do exactly what they sound like they do.
> length "Letterbox"
9
> reverse "Particle Man"
"naM elcitraP"
> minimum [2,4,1,5,7,3]
1
> maximum [2,4,1,5,7,3]
7

Note that the minimum and maximum functions can be used on lists of anything that has an order - i.e. anything in the type class Ord

Here are a few more that might require a bit of explanation.
> take 5 "Sapphire Bullets of Pure Love"
"Sapph"
>take 5 "Dead"
"Dead"

This function gives you a number of elements off the front of the list. If the number you give is bigger than the length of the list, you get the whole thing.

> drop 6 "Your Racist Friend"
"acist Friend"
> drop 6 "Dead"
""
> drop 0 "Dead"
"Dead"

This function cuts a number off the front of the list and gives you the rest. If the number you give is bigger than the length of the list, you get an empty list.


> null "Lucky Ball and Chain"
False
> null []
True

This function evaluates to True if and only if you give it an empty list.

> 'Y' `elem` "Birdhouse In Your Soul"
True
> 'Y' `elem` "Dead"
False

Pretty straightforward; True if the first argument is an element of the second, False otherwise. The first does need to be the same type as the elements of the second, or Haskell will complain. ProTip: Pretty much every two-argument function can be used as an infix function by enclosing it in backticks.

There are also a couple built-in arithmetic list functions. These are self-explanatory.

> sum [1,5,3]
9
> product [1,5,3]
15


Haskell is lazy. This means it doesn't actually evaluate anything until you want to do something with it. This means that we can do infinite lists!

This function will produce an infinite list out of any list by repeating it over and over. If you try to display it, it will never end. We can get around this by just displaying the first few elements, like so:
> take 16 (cycle [1,2,4])
[1,2,4,1,2,4,1,2,4,1,2,4,1,2,4,1]

If you want to repeat just one element, there's a separate function for that. (You can use cycle if you want, though.)
> take 10 (repeat 5)
[5,5,5,5,5,5,5,5,5,5]


Now, sometimes you might want a list like [1,2,3,4,5,6,7,8,9]. Of course, you don't want to type out that whole thing every time. Fortunately for you, Haskell gives you a shortcut: ranges.

Ranges

If you want a list where the elements are consecutive, you can use a range. Like so:
> [1..20]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]

You can use ranges with anything in the type class Enum - like Chars.
> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"


The elements don't actually have to be consecutive; the distance between elements just needs to be constant. You can specify this like so:
> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
> ['b','d'..'z']
"bdfhjlnprtvxz"
> [3,9..99]
[3,9,15,21,27,33,39,45,51,57,63,69,75,81,87,93,99]


We can also count down.
> [20,19..1]
[20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]
> ['z','y'..'a']
"zyxwvutsrqponmlkjihgfedcba"
> [20,18..2]
[20,18,16,14,12,10,8,6,4,2]


You can also use ranges to make infinite lists. All you have to do is not specify an upper limit. Like this:
> take 10 [8,26..]
[8,26,44,62,80,98,116,134,152,170]


Now, remember set-builder notation from math? The one with the curly braces? We can do that in Haskell! They're called list comprehensions.

List Comprehensions

List comprehensions basically work like this: You specify which things you want to do stuff two, and you specify what you want to do to them. Like this:
> [ x^2 | x <- [1..10] ]
[1,4,9,16,25,36,49,64,81,100]

That first line basically says this: A list of x-squared, where x is drawn from the list of things from 1 to 10. You can do interesting things with this. Like this:
> [succ x | x <- "Whistling In The Dark" ]
"Xijtumjoh!Jo!Uif!Ebsl"

Remember that the succ function takes a value and evaluates to whatever value comes next. We'll do more cool stuff with list comprehensions later. But first, what if you want to store a bunch of things of different types in one variable? Yes, Haskell can do that too. You can use tuples.

Tuples
A tuple is basically a set of variables. They're set off with parentheses. Like this:
> (19, "Road Movie To Berlin")
(19, "Road Movie To Berlin")

The type of a tuple is also set off with parentheses, like so:
> :t (19, "Road Movie To Berlin")
(19, "Road Movie To Berlin") :: (Num t) => (t, [Char])

You can nest tuples and lists, so you could create a bizarre monstrosity with a type like this:
(Num t) => [([([Char], t)], Bool)]

That would be a list of tuples, each composed of two elements: a list of tuples, each containing a list of characters and a number, and a boolean.

A tuple containing two values is called a pair, and there are two special functions that you can use on them:
> fst (5, "Dead")
5
> snd (5, "Dead")
"Dead"

That's the first and second value in the tuple, respectively.

Remember, the difference between a tuple and a list: A list is a set of no specific size where the elements are all the same type; a tuple is a set of specific size where the elements can be of different types.

Next time, we'll finally do some actual coding! (Well, I will. I can't guarantee that you will.)

No comments:

Post a Comment