The Little Haskeller ==================== by Curt Sampson $Revision$ $Date$ This is a translation of the code from _The Little MLer_ into Haskell. It's divided into sections by chapter, and numbered according to the Q&A numbers in the text of each chapter. It's currently in quite an early state, and rather rough and ready. I intend to continue adding translations as I go through the book myself, clean up things as I learn more and receive comments, and perhaps do a big final cleanup at the end. Please feel free to mail me any comments you have, preferably including the revision number of the document above. I would appreciate feedback from both Haskell experts and novices who are using this. This is "literate Haskell," which means that the actual code is surrounded by blank lines and preceeded by a ">" symbol on each line. If this file is saved as a ".lhs" file, it can be read directly into the ghci or Hugs interpreters (using the ":l" command), and you can play with the definitions. 1. Building Blocks ------------------ 16. The ML "datatype" command in Haskell is just "data". We add "deriving show" after the data definition so that the interpreter or compiler can print out the value of this; try typing just "Salt" at your interpreter prompt both with and without the "deriving Show" added to the type definition. > data Seasoning = Salt | Pepper deriving Show 21. Here we use "Number" instead of "Num", because "Num" is already defined in the Prelude as a Haskell type class. (Don't worry yet about what a type class is; it's similar to a type.) And we don't need the "of" between a constructor (OneMoreThan) and the type of the data it can take as a parameter (Number). > data Number = Zero | OneMoreThan Number deriving Show 23. The Haskell syntax doesn't use parentheses for arguments to functions or constructors (which are really just a kind of function). In this example, we assign this to a name so that it parses as a Haskell file, but when typing at the interpreter prompt, you can leave off everything up to the "=" sign. > ch1sec23 = OneMoreThan Zero 25. Haskell, not having parentheses to figure out where the list of arguments to a function ends, but it does know how many arguments a function takes, so it tries to use that many. In this case, it knows that OneMoreThan takes one argument, so if we tried to type OneMoreThan OneMoreThan Zero -- Invalid! it would assume that the argument to the first OneMoreThan is the thing after it, which is "OneMoreThan". But OneMoreThan is not a number, it's a function that makes a Number, so this will fail. We really want to apply the second OneMoreThan to the Zero, first, getting a Number, and then pass the Number resulting from that to the first OneMoreThan. We uses parentheses to force this order of evaluation. > ch1sec25 = OneMoreThan (OneMoreThan Zero) 26. Invalid code examples, like this one and the one above, will not have a ">" in front of them, because of course they can't be parsed by the interpreter. And we'll put a little comment after invalid code, just to make it clear. OneMoreThan 0 -- Invalid! 27. From this point on, we may leave out textual explanation and give you just the code samples in the format below. You can tell from the name that the example has been assigned to which chapter and section it's from. > ch1sec27 = OneMoreThan (OneMoreThan (OneMoreThan (OneMoreThan Zero))) 32. We use just the letter "a" in Haskell as a type variable, rather than "'a", and the type variable goes after the type name, rather than before. > data OpenFacedSandwich a = Bread a | Slice (OpenFacedSandwich a) > deriving Show > ch1sec33 = Bread 0 34. A slight difference in booleans; in Haskell we have no automatic definition "true"; instead we construct a new True value of type boolean using the no-argument constructor for it which comes from the definition of Bool in the prologue: data Bool = False | True > ch1sec34 = Bread True 35. The types can be found with the ":t" command in your interpreter; e.g., ":t Bread True" should produce something like: Bread True :: OpenFacedSandwich Bool which is a proper Haskell definition. 37. The definitions are also ungramatatical in Haskell: data OpenFacedSandwich Int -- Invalid! = Bread Int | Slice (OpenFacedSandwich_I Int) 43. In Haskell, we can declare the type of an expression along with the expression. > ch1sec43 = Bread 0 :: OpenFacedSandwich Int Of course, if we got it wrong, the compiler would tell us: Bread True :: OpenFacedSandwich Int -- Invalid > ch1sec45 = Bread (OneMoreThan Zero) :: OpenFacedSandwich Number 46. We declare the type within OpenFacedSandwich below to be Int, > ch1sec46 = Bread (Bread 0) :: OpenFacedSandwich (OpenFacedSandwich Int) but really, "0" in Haskell might be a float, or other kind of number. The type system tries to figure out what it should use based where it's used and what's wanted in that context. The general type is actually the type class "Num", so if you just ask the interpreter :t Bread 0 You will get this: Bread 0 :: (Num t) => OpenFacedSandwich t That just says that "t" is one of many types that is of class "Num"; you don't have to worry about this at this point in the book. If ever it does confuse you, you can always declare a type for the literal number you type in: :t Bread (0 :: Int) will give you the more familiar Bread (0 :: Int) :: OpenFacedSandwich Int 47. And the type of: > ch1sec47 = Bread (Bread (OneMoreThan Zero)) is OpenFacedSandwich (OpenFacedSandwich Number) XXX Editor's note: we should insert a "moral" here about how one translates function application from ML to Haskell. Something about the parens.... 2. Matchmaker, Matchmaker ------------------------- 1. > data ShishKebab = Skewer > | Onion ShishKebab > | Lamb ShishKebab > | Tomato ShishKebab > deriving Show > ch2sec3 = Onion Skewer > ch2sec4 = Onion (Lamb (Onion Skewer)) From here, with the aid of the examples above, you should be able to fairly easily convert from the ML constructor form Onion(Lamb(Onion(Skewer)) to the Haskell form given in ch2sec4 above. So we'll leave out the simpler ones from this point on. 15. Note that in Haskell the type declaration of a definition must come before the definition itself. So the two boxes in the book are reversed below. > onlyOnions :: ShishKebab -> Bool > onlyOnions Skewer = True > onlyOnions (Onion x) = onlyOnions x > onlyOnions (Lamb x) = False > onlyOnions (Tomato x) = False Note that pattern matching expressions need to be parenthesized just as regular expressions do; we want to say that "Onion applied to x" is the single argument to onlyOnions in the pattern "onlyOnions (Onion x)". Leaving out the parenthesis would say that onlyOnions takes two arguments, the first of which is a function that takes something and returns a ShishKebab, and the second of which is something. (Normally the type declaration would make clear what these are.) In this case, since that doesn't match the type declaration, you'd get an error, in ghc's case, "Equations for `onlyOnions' have different numbers of arguments." > ch2sec24 = onlyOnions (Onion (Onion Skewer)) 42. The successive applications of the function: > ch2sec42a = onlyOnions (Onion (Onion Skewer)) -- is the same as: > ch2sec42b = onlyOnions (Onion Skewer) -- which is the same as: > ch2sec42c = onlyOnions Skewer -- which is: > ch2sec42d = True > ch2sec43 = onlyOnions (Onion (Lamb (Skewer))) 63. > isVegetarian :: ShishKebab -> Bool > isVegetarian Skewer = True > isVegetarian (Onion x) = isVegetarian x > isVegetarian (Lamb x) = False > isVegetarian (Tomato x) = isVegetarian x 64. Well, we're definiting all of this in one module (file), and we can't have multiple constructors with the same name. Otherwise, the interpreter can't know, when you say "Onion foo", whether you're trying to make a ShishKebab or a Shish. So we prepend an 'S' to the Shish constructors to resolve that problem. If you get odd type errors later on in this chapter, check that you're using the right constructor! Trust me, you will make this mistake. > data Shish a = Bottom a > | SOnion (Shish a) > | SLamb (Shish a) > | STomato (Shish a) > deriving Show 65. > data Rod = Dagger | Fork | Sword deriving Show > data Plate = GoldPlate | SilverPlate | BrassPlate deriving Show > ch2sec69 = SOnion (STomato (Bottom Dagger)) You'll note that in Haskell, we say that this belongs to "Shish Rod", the opposite of ML's "rod shish". Type ":t ch2sec69" at your interpreter prompt to see: ch2sec69 :: Shish Rod 73. > isVeggie :: Shish a -> Bool > isVeggie (Bottom x) = True > isVeggie (SOnion x) = isVeggie x > isVeggie (SLamb x) = False > isVeggie (STomato x) = isVeggie x 74. isVeggie (Onion Fork) -- Invalid! > ch2sec76 = isVeggie (SOnion (STomato (Bottom Dagger))) XXX Editor's note: The rest of chapter two is coming soon.