[More progress on chapter 3. Bryan O'Sullivan **20070716064528] { hunk ./en/ch02-starting.xml 328 - + hunk ./en/ch03-funcs-types.xml 6 + Topics: built-in types. Writing functions. Creating new + types. + hunk ./en/ch03-funcs-types.xml 13 - inference. For a short sentence, this one has a lot - for us to pore over. Because Haskell is quite different from - mainstream programming languages in how it treats types, let's - not assume anything as we talk about types. + inference. For a short sentence, this one gives us a + lot for us to pore over. Because Haskell is quite different + from mainstream programming languages in how it treats types, + let's not assume too much shared understanding as we talk about + types. hunk ./en/ch03-funcs-types.xml 84 - character set. + character set, which covers most of the world's written + languages. hunk ./en/ch03-funcs-types.xml 102 - integer of arbitrary width. Integer values are - not used as often as Ints, because they're a - lot more expensive to work with. + integer of arbitrary size. Integers are not + used as often as Ints, because they're a lot + more expensive to work with. hunk ./en/ch03-funcs-types.xml 110 - representation. + representation. (A narrower type, Float, also + exists, but its use is discouraged; Haskell compiler writers + concentrate more on making Double + efficient.) hunk ./en/ch03-funcs-types.xml 139 + Lists are the bread and butter of Haskell + collections. Whereas in an imperative language, we might repeat + a task over many items by iterating through a loop, this is + something that we tend to do in Haskell by recursing over a + list. + hunk ./en/ch03-funcs-types.xml 146 - can be of any type. Unlike a list, the elements of which must - all have the same type, there's no need for the elements of a - tuple to be related. We write a tuple by enclosing its elements - in parentheses and separating them with commas. We use the same - notation for writing its type. + can be of any type. Unlike a list (the elements of which must + all have the same type), there's no need for the elements of a + tuple to have related types. We write a tuple by enclosing its + elements in parentheses and separating them with commas. We use + the same notation for writing its type. hunk ./en/ch03-funcs-types.xml 157 - elements in its own type, so tuples containing different numbers - or types of elements have different types of their own. + elements in its own type. This means that tuples containing + different numbers or types of elements have distinct types of + their own. hunk ./en/ch03-funcs-types.xml 162 - + + A two-tuple of an Int and a String + has a different type than a two-tuple of a Bool and + a Bool, for example, and a three-tuple has a + different type than a four-tuple. + + Probably the most common use of tuples is to let us return + multiple values from a function. We can also use them in other + places where we want a fixed-size collection of values, for + which the kind of container we're using isn't of much + importance. An example of this might be to represent a row + in the result of a a database query. + + + + Calling functions + + Now that we've had our fill of data types for a while, let's + turn our attention to working with some of + the types we've seen. We've already seen how to perform + arithmetic in , and it + looks quite as it does in other languages. + + However, our discussion of lists and tuples mentioned how we + can construct them, but not how we do anything with them + afterwards. Haskell defines a large library of functions for + working with lists and (to a lesser extent) tuples, so let's + find out how to use a few of those functions. + + To call a function in Haskell, we provide the name of the + function followed by its arguments, all separated by whitespace. + As an example, let's call the head + function, which returns the first element of a list. + + &func.ghci:head; + + Its counterpart, tail, returns all + but the head of a list. + + &func.ghci:tail; + + A related pair of functions, take and + drop, take two arguments: given a number + and a list, they return either the first, or all but the first, + number of elements of the list. (Here, remember that a + String is a list of Char.) + + &func.ghci:takeDrop; + + If you're used to function call syntax in other languages, + this notation can take a little getting used to, but it's + undeniably simple and uniform. Here's what we mean by + uniform. + + &func.ghci:snd; + + In some other languages, the call to + snd above might mean call + snd with two arguments, + 1 and 2, but in + Haskell, it's a single-argument call, passing the tuple + (1,2). + + By the way, snd has a companion + function, fst, which returns the first + element of a tuple. + + &func.ghci:fst; + + + Passing an expression to a function + + Haskell evaluates an expression from left to right. If we + want to use the result of one expression as an argument to + another, we have to keep this in mind and use parentheses to + tell the parser what we really mean. Here's an + example. + + &func.ghci:headDrop; + + We can read this as pass the result of the + expression drop 4 "azerty" as the argument to + head. If we were to leave out + the parentheses, Haskell would instead interpret the + expression as pass drop as the + argument to head. Compilation + would fail with a type error, as drop is + a function, not a list. + + + + + Understanding a function's type signature + + A consequence of Haskell's strong typing is that + fst and snd only + accept two-tuples as arguments. Let's see what &ghci; tells us + about their types. + + &func.ghci:fst.type; + + We can read the -> above as + returns. The entire signature thus tells us that + fst takes any two-tuple whose elements are + of types a and b (each of which can be of any type), + and returns a value with type a. + Sure enough, if we try calling fst with a + three-tuple, things don't go so well. + + &func.ghci:fst.bad; + + Notice that the type signatures above give us a strong hint + as to what these functions might actually + do. This is an incredibly valuable property + of types in a functional language. Since there aren't usually + any side effects for us to worry about, figuring what a function + does can often be a matter of reading its name and understanding + its type signature, with no regular documentation + required. + + So far, we haven't seen a signature for a function that + takes more than one argument. We've + already encountered a few such functions; let's look at + take. + + &func.ghci:take.type; + + It's pretty clear that there's something going on with an + Int and some lists, but why are there two + -> symbols in the signature? Haskell + parses these from right to left. If we introduce parentheses, + it makes it clearer how Haskell is interpreting this type + signature. + + &Take.hs:type; + + From this, it looks like we ought to read the type signature + as a function that takes one argument, an Int, and + returns another function. That other function also takes one + argument, a list, and returns a list of the same type as its + argument. + + This is an intriguing idea, but it's not easy to see just + yet what its consequences might be. We'll return to this topic + soon, once we've spent a bit of time writing functions. + + + + + Haskell source files, and writing simple functions + + Now that we know how to call functions, it's time we turned + our attention to writing them. While we can write functions in + &ghci;, it's not a good environment for doing so, because it + limits any expression or definition to one line in length. So + instead, we'll finally break down and create a source + file. + + Haskell source files are usually identified with a suffix of + .hs. Here's a simple function definition: + open up a file named add.hs, and add these + contents to it. + + &add.hs:add; + + On the left hand side of the = is the + name of the function, followed by the arguments to the function. + On the right hand side is the body of the function. With our + source file saved, we can load it into &ghci;, and use our new + add function straight away. + + &add.ghci:add; + + When we call add, the variables + a and b on the left hand + side of our definition are given (or bound to) + the values 1 and 2, then + the right hand side is evaluated, and the result + returned. + + Haskell doesn't need a return statement; + the result of a function is the result of evaluating whatever + expression is in the function's body. + + + Conditional evaluation + + Like other languages, Haskell has an if + expression. Let's see it in action: we'll write our own + version of the standard take function. + Before we do so, let's probe a little bit of how + take behaves. + + &myTake.ghci:take; + + From the above, it seems that take + removes at most the given number of elements from the front of + a list (it returns the empty list if the number to remove is + greater than the number of elements), and that it treats + negative numbers as zero. + + Here's a myTake function that has the + same behaviour as take, and uses + Haskell's if expression. + + &myTake.hs:myTake; + + Let's save it in a file named + myTake.hs, then load it into + &ghci;. + + &myTake.ghci:myTake; + + Now that we've seen myTake in action, + let's return to the source code and look at a few of the + novelties we've introduced. + + First is the if keyword itself. It takes an + expression of type Bool. If that evaluates to + True, it evaluates the expression on the + then branch. Otherwise, it evaluates the + expression on the else branch. + + The combination of if, then, + else and the expression after each keyword + combine to make up a single expression, as far as Haskell is + concerned. Whichever branch is evaluated is the result of the + if. Because an expression can only have one + type, the expressions in the then and + else branches must have the same type. If they + don't, an if expression won't typecheck. + + Whereas it can make sense in an imperative language to + omit the else branch from an if, + this would be nonsensical in Haskell. An if + expression that was missing an else couldn't + typecheck, since it would sometimes have a value and sometimes + not. + + The second novelty is almost trivial: the + null function, which we use in the + Boolean portion of the if, indicates whether a + list is empty. + + &myTake.ghci:null; + + Finally, our if expression spans several + lines. We line the then and else + branches up under the if for neatness, but this + is not mandatory. We could put all of them on a single line, + for example, but this is harder to read. + + &myTake.hs:myTake2; + + XXX Make a forward reference to discussion of the layout + rule. + + hunk ./en/ch03-funcs-types.xml 426 - We introduce a new data type using the data - keyword. + Although lists and tuples are useful, we'll still often want + to construct new data types of our own. We define a new data + type using the data keyword. hunk ./en/ch03-funcs-types.xml 666 + We can use (:) repeatedly to add new + elements to the front of a list. + + &list.ghci:cons2; + + The right hand side of (:) must be a + list, and of the correct type. If it's not, we'll get an + error. + + &list.ghci:cons.bad; + addfile ./examples/ch03/Take.hs hunk ./examples/ch03/Take.hs 1 +{-- snippet type --} +take :: Int -> ([a] -> [a]) +{-- /snippet type --} +take = undefined addfile ./examples/ch03/add.ghci hunk ./examples/ch03/add.ghci 1 +--# add +:load add.hs +add 1 2 addfile ./examples/ch03/add.hs hunk ./examples/ch03/add.hs 1 +{-- snippet add --} +add a b = a + b +{-- /snippet add --} addfile ./examples/ch03/func.ghci hunk ./examples/ch03/func.ghci 1 +--# head + +head [1,2,3] + +--# tail + +tail [True, False] + +--# takeDrop + +take 2 "abcdefg" +drop 3 "abcdefg" + +--# snd + +snd (1,2) + +--# fst + +fst ("you", True) + +--# fst.type + +:type fst +:type snd + +--# fst.bad + +fst (1,True,"foo") + +--# take.type + +:type take + +--# headDrop + +head (drop 4 "azerty") hunk ./examples/ch03/list.ghci 8 +1 : [2] + +--# cons2 + +"alpha" : "beta" : ["gamma", "delta"] + +--# cons.bad + +True : False +True : ["wrong"] addfile ./examples/ch03/myTake.ghci hunk ./examples/ch03/myTake.ghci 1 +--# take + +take 0 "foo" +take 1 "foo" +take 4 [1,2] +take 7 [] +take (-2) "foo" + +--# myTake + +:load myTake.hs + +myTake 0 "foo" +myTake 1 "foo" +myTake 4 [1,2] +myTake 7 [] +myTake (-2) "foo" + +--# null + +:type null addfile ./examples/ch03/myTake.hs hunk ./examples/ch03/myTake.hs 1 +{-- snippet myTake --} +myTake :: Int -> [a] -> [a] + +myTake n xs = if n <= 0 || null xs + then xs + else myTake (n - 1) (tail xs) +{-- /snippet myTake --} + +{-- snippet myTake2 --} +myTake2 n xs = if n <= 0 || null xs then xs else myTake (n - 1) (tail xs) +{-- /snippet myTake2 --} }