简体   繁体   中英

Haskell: arithmetic operations on data types

I'm trying to understand data types and to do that, I want to try to emulate integer values. I defined it in the following way:

data Number = Zero | One | Two | Three deriving (Eq,Ord,Show)

So basically, I want to do be able to do basic arithmetic operations with elements of this type. Something akin to

addNumber :: Number -> Number -> Number

called like addNumber One Two would give Three as a result. However, I'm really not sure how to do this properly. I'd assume it would be in a way possible to do this by comparing Number's to each other, but to get anything from that, I would need to be able to access the next number in a this particular ordering, but I don't know how you'd do that with the given data type. So far, I'm doing something like this:

getIntDex :: Number -> Int
getIntDex n = intDex n 0 nList

nList :: [Number]
nList = [Zero, One, Two, Three]

intDex :: Number -> Int -> [Number] -> Int
intDex e i (x:xs) = if((compare e x) == EQ)
            then i
            else intDex e (i+1) xs

Which at least "converts" it into an integer so I can actually do something arithmetic with it. However, this feels kinda static and overall wrong (and I could probably do this faster with a switch or guards or something similar. Is there a better way?

I will assume you wish to avoid using the built-in arithmetic -- if not, as you observe one could convert to built-in numbers, do arithmetic, and convert back out, but that's not very satisfying from a "learning Haskell" perspective.

The standard way to define new functions on new data types is with pattern matching. For example, one naive approach to your problem would be to list all pairs of input Number s together with their sum:

add :: Number -> Number -> Number
add Zero  Zero  = Zero
add Zero  One   = One
add Zero  Two   = Two
add Zero  Three = Three
add One   Zero  = One
add One   One   = Two
-- ...

Of course, from a programmer's perspective that looks a bit daunting and boiler-plate-y. On the other hand, splitting out a few functions could make it less so; for example, you might write a function to add one and iterate it:

addOne :: Number -> Number
addOne Zero  = One
addOne One   = Two
addOne Two   = Three
addOne Three = Zero

add :: Number -> Number -> Number
add Zero  = id
add One   = addOne
add Two   = addOne . addOne
add Three = addOne . addOne . addOne

Of course, this will be slightly less efficient; and you wouldn't want to do this for larger number types. But it requires a lot less finger typing. (Anyway for larger number types you would probably want a different implementation than a big enumeration -- eg a stream of bits -- but that's beside the point here I think.)

If you also derive an Enum instance, you can use fromEnum :: Enum a => a -> Int and toEnum :: Enum a => Int -> a to convert between your data types and integers corresponding to their position in the data definition. Since you have Zero in the 0th position, and so on, this will be exactly what you want for going to and from integers.

But you should also consider: what will be the result of addNumber Three Two ? There's no correct value there, because your definition of numbers doesn't go up to Five. Whatever upper bound you set, you will have a "partial" function, which is undefined for some values in its domain. Perhaps this is fine for you, if you're just doing an exercise on data types, but generally in Haskell programs we try to avoid partial functions, because they can cause runtime errors in cases that "should" have been caught at compile time. You could, for example, return Maybe Number instead of Number , and then return Nothing if there's no valid answer. Then the caller can explicitly cope with the possibility of failure, instead of implicitly accepting it and suffering an exception.

Your numbers are extremely unstructured. As such, if you want to define addition directly (rather than via a detour with the standard type Int and using the standard + ), you will have to do this:

addNumber Zero n = n -- Zero added to anything is n
addNumber One One = Two
addNumber One Two = Three
addNumber Two One = Three
addNumber _ _ = error "overflow, we only go up to Three"

exhaustively listing every case (which, fortunately, is not very many since you only went up to Three !).

You can imagine other slightly more structure numbers where you can use the structure to define addition more economically. The famous example is

data N = Z | S N

... a recursive data type for 'Peano' numbers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM