[Document the offside rule. Bryan O'Sullivan **20070718081627] { addfile ./examples/ch03/BadIndent.hs addfile ./examples/ch03/Braces.hs addfile ./examples/ch03/GoodIndent.hs addfile ./examples/ch03/indent.ghci addfile ./examples/ch03/letwhere.hs hunk ./en/ch03-funcs-types.xml 658 - them referring to each other. + them referring to each other. (In some functional languages, + this sort of flexible let is named + letrec.) hunk ./en/ch03-funcs-types.xml 662 - We sneaked a previously unseen standard type, - Maybe, into our example. We use Maybe - when it might not make sense to return a normal result, for - example because a function's result is undefined for some - inputs. We use Just to say we have - a result, and the argument to - Just is that result. When we can't give a - result, we use Nothing, which takes no - arguments. + We can have multiple let blocks within an + expression. There's also another mechanism we can use to + introduce local variables, called a where block. + The definitions in a where block apply to the code + that precedes it. Let's illustrate what we + mean with another example. + + &Roots.hs:roots; + + Here, the roots function returns the + real roots when they're defined, and the complex roots + otherwise. (We left out the divide-by-zero case for simplicity.) + While a where clause initially looks very weird + to non-Haskell programmers, it's a great way to put the + important code early, followed by the auxiliary + definitions that support it. After a while, you'll find + yourself missing where clauses in languages that + lack them! + + The main difference between let and + where is one of scope. The scope of a + let only extends to the expression after the + in keyword, while the variables introduced by a + where clause are visible upwards to the beginning + of the block that it belongs to. Also, + let is always paired with an expression, but + where is paired with a block of equations. + + We'll be talking more about how to write let + expressions and where clauses in . + + + It's probably obvious from context above, but + (:+) is the constructor for a complex + number, taking the real part on the left and the imaginary + part on the right. + + Also, Complex is parameterised over the type + of complex number it should represent. In practice, it only + makes much sense to use Complex Double, since + &GHC; implements Double more efficiently than + Float. + + + + + How to represent a complicated result + + We sneaked two previously unseen standard types, + Maybe and Either, into our + root-finding examples. We use Maybe when it might + not make sense to return a normal result, for example because a + function's result is undefined for some inputs. We use + Just to say we have a + result, and the argument to Just + is that result. When we can't give a result, we use + Nothing, which takes no arguments. hunk ./en/ch03-funcs-types.xml 741 + The Either a b type gives us more flexibility, + as it's got two type parameters. We can wrap a value of any + type a with Left, or + a value of an unrelated type b + with Right. Our roots + function uses this to return a Double when the real + roots are defined, and a Complex Double when + they're not. hunk ./en/ch03-funcs-types.xml 751 - - The offside rule, and white space in a function body + + The offside rule, and white space in a function + body hunk ./en/ch03-funcs-types.xml 758 + + Haskell uses indentation as a cue to parse sections of code. + This use of layout to convey structure is sometimes called the + offside rule. At the top level, the first declaration or + definition can start in any column, and the Haskell compiler or + interpreter remembers that indentation level. Every subsequent + top-level declaration must have the same indentation. + + Here's an illustration of the top-level indentation rule. + Our first file, GoodIndent.hs, is well + behaved. + + &GoodIndent.hs:good; + + Our second, BadIndent.hs, doesn't play + by the rules. + + &BadIndent.hs:bad; + + Here's what happens when we try to load the two files into + &ghci;. + + &indent.ghci:load; + + An empty line is treated as a continuation of the current + item, as is a line indented to the right of the current current + item. + + The rules for let expressions and + where clauses are similar. After a + let or where keyword, the Haskell + compiler or interpreter remembers the indentation of the next + token it sees. If the next line is empty, or its indentation is + further to the right than the previous line, this counts as + continuing the previous line. On the other hand, if the + indentation is the same as the previous line, this is treated as + beginning a new item in the same block. + + Here are nested uses of let and + where. + + &letwhere.hs:let; + + In the body of bar, the variable + a is only visible within the let + expression that defines it. It's not visible to the + let expression that defines b; + only the result of the inner + let expression is visible. + + &letwhere.hs:where; + + Similarly, the scope of the first where clause + is the definition of foo, but the scope of + the second is just the first where clause. + + The indentation we use for the let and + where clauses makes our intentions easy to figure + out. + + + A note about tabs versus spaces + + If you are using a Haskell-aware text editor (e.g. Emacs), + it is probably already configured to use space characters for + all white space within a line. If your editor is + not Haskell-aware, you should configure + it to only use space characters. + + The reason for this is simple portability. In an editor + that uses a fixed-width font, tab stops are by default placed + at different intervals on Unix-like systems (every eight + characters) than on Windows (every four characters). This + means that no matter what your personal beliefs are about + where tabs belong, you can't rely on someone else's editor + honouring your preferences. Any indentation that uses tabs is + going to look broken under someone's + configuration; this could lead to compilation problems, too. + Using space characters instead avoids this problem + entirely. + + + + The offside rule is not mandatory + + We can use explicit structuring instead of layout to + indicate what we mean. To do so, we start a block of + equations with an opening curly brace; separate each item with + a semicolon; and finish the block with a closing curly brace. + The following two uses of let have the same + meanings. + + &Braces.hs:braces; + + When we use explicit structuring, the normal layout rules + don't apply, which is why we can get away with unusual + indentation in the second let expression. + + We can use explicit structuring anywhere that we'd + normally use layout. It's valid for where + clauses, and even top-level declarations. Just remember that + although the facility exists, explicit structuring is hardly + ever actually used in Haskell + programs. + hunk ./examples/ch03/BadIndent.hs 1 +{-- snippet bad --} +-- This is the leftmost column. + + -- Our first declaration is in column 4. + firstBadIndentation = 1 + + -- Our second is left of the first, which is illegal! + secondBadIndentation = 2 +{-- /snippet bad --} hunk ./examples/ch03/Braces.hs 1 +{-- snippet braces --} +bar = let a = 1 + b = 2 + c = 3 + in a + b + c + +foo = let { a = 1; b = 2; + c = 3 } + in a + b + c +{-- /snippet braces --} hunk ./examples/ch03/GoodIndent.hs 1 +{-- snippet good --} +-- This is the leftmost column. + + -- It's fine for top-level declarations to start in any column... + firstGoodIndentation = 1 + + -- ...provided all subsequent declarations do, too! + secondGoodIndentation = 2 +{-- /snippet good --} hunk ./examples/ch03/Roots.hs 1 +{-- snippet roots --} hunk ./examples/ch03/Roots.hs 4 +roots :: Double -> Double -> Double + -> Either (Complex Double, Complex Double) (Double, Double) + +roots a b c = if n > 0 + then Right ((-b + sqrt n) / a2, (-b - sqrt n) / a2) + else Left ((-b' + sqrt n') / a2', (-b' - sqrt n') / a2') + where n = b**2 - 4 * a * c + a2 = 2 * a + n' = n :+ 0 + b' = b :+ 0 +{-- /snippet roots --} + hunk ./examples/ch03/Roots.hs 28 -roots :: Double -> Double -> Double - -> Either (Complex Double, Complex Double) (Double, Double) - -roots a b c = let n = b**2 - 4 * a * c - a2 = 2 * a - n' = n :+ 0 - b' = b :+ 0 - a2' = a2 :+ 0 - in if n > 0 - then Right ((-b + sqrt n) / a2, (-b - sqrt n) / a2) - else Left ((-b' + sqrt n') / a2', (-b' - sqrt n') / a2') - hunk ./examples/ch03/indent.ghci 1 +--# load +:load GoodIntent.hs +:load BadIndent.hs hunk ./examples/ch03/letwhere.hs 1 +{-- snippet where --} +foo = a + where a = b + where b = 2 +{-- /snippet where --} + +{-- snippet let --} +bar = let b = 2 + in let a = b + in a +{-- /snippet let --} }