[More work on num John Goerzen **20070921105116] { hunk ./en/ch06-typeclasses.xml 948 + + + For an extended example demonstrating the use of these numeric + typeclasses, see . + hunk ./en/ch13-data.xml 564 + + Extended example: Numeric Types + + We've told you how powerful and expressive Haskell's type system is. + We've shown you a lot of ways to use that power. Here's a chance to + really see that in action. + + + Back in , we showed + the numeric typeclasses that come with Haskell. Let's see what we can + do by defining new types and utilizing the numeric typeclasses to + integrate them with basic mathematics in Haskell. + + + Let's start by thinking through what we'd like to see out of &ghci; + when we interact with our new types. To start with, it might be nice + to render numeric expressions as strings, making sure to indicate + proper precedence. Perhaps we could create a function called + prettyShow to do that. + + &num.ghci:prettyshow; + + That looks nice, but it wasn't all that smart. We could easily + simplify out the 1 * part of the expression. How + about a function to do some very basic simplification? + + &num.ghci:simplify; + + How about converting a numeric expression to Reverse Polish Notation + (RPN)? RPN is a postfix notation that never requires parentheses, and + is commonly found on HP calculators. RPN is a stack-based notation. + You enter numbers onto the stack, and when you enter operations, they + pop the most recent numbers off the stack and place the result on the + stack. + + &num.ghci:rpnshow; + + Maybe it would be nice to be able to represent simple expressions with + symbols for the unknowns. + + &num.ghci:symbols; + + It's often important to track units of measure when working with + numbers. For instance, when you see the number 5, does it mean 5 + meters, 5 feet, or 5 bytes? Of course, if you divide 5 meters by 2 + seconds, the system ought to be able to figure out the appropriate + units. Moreover, it should stop you from adding 2 seconds to 5 meters. + + &num.ghci:units; + + If we define an expression or a function that is valid for all numbers, + we should be able to calculate the result, or render the expression. + For instance, if we define test to have type + Num a => a, and say test = 2 * 5 + + 3, then we ought to be able to do this: + + &num.ghci:func; + + Since we have units, we should be able to handle some basic + trigonometry as well. Many of these operations operate on angles. + Let's make sure that we can handle both degrees and radians. + + &num.ghci:trig; + + Finally, we ought to be able to put all this together and combine + different kinds of expressions together. + + &num.ghci:final; + + Perhaps a future excercise could enhance prettyShow + to remove unnecessary parentheses as well. + + + Everything you've just seen is possible with Haskell types and classes. + In fact, you've been reading a real &ghci; session demonstrating + num.hs, which you'll see shortly. + + + First Steps + + Let's think about how we would accomplish everything shown above. To + start with, we might use &ghci; to check the type of + (+), which is Num a => a -> a -> + a. If we want to make possible some custom behavior for + the plus operator, then we will have to define a new type and make it + an instance of &Num;. This type will need to store an expression + symbolically. We can start by thinking of operations such as addition. + To store that, we will need to store the operation itself, its left + side, and its right side. The left and right sides could themselves be + expressions. + + + We can therefore think of an expression as a sort of tree. Let's start + with some simple types. + + &numsimple.hs:all; + + First, we define a type called Op. This type + simply represents some of the operations we will intend to support. + Next, there is a definition for SymbolicManip a. + Because of the Num a constraint, any + Num can be used for the a. So + a full type may be something like SymbolicManip + Int. + + + A SymbolicManip type can be a plain number, or it + can be some arithmetic operation. The type for the + Arith constructor is recursive, which is perfectly + legal in Haskell. Arith creates a + SymbolicManip out of an Op and + two other SymbolicManip items. Let's look at an + example: + + &numsimple.ghci:all; + + You can see that we already have a very basic representation of + expressions working. Notice how Haskell "converted" 5 * 10 + + 2 into a SymbolicManip, and even + handled order of evaluation properly. This wasn't really a true + conversion; SymbolicManip is a first-class number + now. Integer numeric literals are internally treated as being wrapped + in &fromInteger; anyway, so 5 is just as valid as + a SymbolicManip Int as it as an + Int. + + + From here, then, our task is simple: extend the + SymbolicManip type to be able to represent all the + operations we will want to perform, implement instances of it for the + other numeric typeclasses, and implement our own instance of &Show; + for SymbolicManip that renders this tree in a more + accessible fashion. + + + + Completed Code + + Here is the completed num.hs, which was used with + the &ghci; examples at the beginning of this chapter. + + &num.hs:all; + + Here we have done what we set out to accomplish: implemented more + instances for SymbolicManip. We have also + introduced another type called Units which stores + a number and a unit of measure. We implement several show-like + functions which render the SymbolicManip or + Units in different ways. + + + There is one other point that this example drives home. In many + languages, even those with objects and overloading, there are still + some parts of the language that are special in some way. In Haskell, + the "special" bits are extremely small. We have just developed a new + representation for something as fundamental as a number, and it has + been really quite easy. Haskell takes code reuse and + interchangability to the extreme. It is easy to make code generic + and work on things of many different types. It's also easy to make + up new types and make them automatically be + first-class features of the system. + + + + addfile ./examples/ch13/num.ghci hunk ./examples/ch13/num.ghci 1 +--# prettyshow +:l num.hs +5 + 1 * 3 +prettyShow $ 5 + 1 * 3 +prettyShow $ 5 * 1 + 3 +--# simplify +prettyShow $ simplify $ 5 + 1 * 3 +--# rpnshow +rpnShow $ 5 + 1 * 3 +rpnShow $ simplify $ 5 + 1 * 3 +--# symbols +prettyShow $ 5 + (Symbol "x") * 3 +--# units +5 / 2 +(units 5 "m") / (units 2 "s") +(units 5 "m") + (units 2 "s") +(units 5 "m") + (units 2 "m") +(units 5 "m") / 2 +10 * (units 5 "m") / (units 2 "s") +--# trig +sin (pi / 2) +sin (units (pi / 2) "rad") +sin (units 90 "deg") +(units 50 "m") * sin (units 90 "deg") +--# func +test +rpnShow test +prettyShow test +test + 5 +prettyShow (test + 5) +rpnShow (test + 5) +--# final +((units 50 "m") * sin (units 90 "deg")) :: Units (SymbolicManip Double) +prettyShow $ dropUnits $ (units 50 "m") * sin (units 90 "deg") +rpnShow $ dropUnits $ (units 50 "m") * sin (units 90 "deg") +(units (Symbol "x") "m") * sin (units 90 "deg") + hunk ./examples/ch13/num.hs 87 -and will add parenthesis when not needed in some cases. -} +and will add parenthesis when not needed in some cases. + +Haskell will have already figured out precedence for us while building +up the SymbolicManip. -} addfile ./examples/ch13/numsimple.ghci hunk ./examples/ch13/numsimple.ghci 1 +--# all +:l numsimple.hs +Number 5 +:t Number 5 +:t Number (5::Int) +Number 5 * Number 10 +(5 * 10)::SymbolicManip Int +(5 * 10 + 2)::SymbolicManip Int addfile ./examples/ch13/numsimple.hs hunk ./examples/ch13/numsimple.hs 1 +{- snippet all -} +-- The "operators" that we're going to support +data Op = Plus | Minus | Mul | Div | Pow + deriving (Eq, Show) + +{- The core symbolic manipulation type -} +data Num a => SymbolicManip a = + Number a -- Simple number, such as 5 + | Arith Op (SymbolicManip a) (SymbolicManip a) + deriving (Eq, Show) + +{- SymbolicManip will be an instance of Num. Define how the Num +operations are handled over a SymbolicManip. This will implement things +like (+) for SymbolicManip. -} +instance Num a => Num (SymbolicManip a) where + a + b = Arith Plus a b + a - b = Arith Minus a b + a * b = Arith Mul a b + negate a = Arith Mul (Number (-1)) a + abs a = error "abs is unimplemented" + signum _ = error "signum is unimplemented" + fromInteger i = Number (fromInteger i) +{- /snippet all -} }