简体   繁体   English

Haskell:对数据类型的算术运算

[英]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. 像addNumber一二这样调用将得到三。 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. 我认为可以通过相互比较Number来做到这一点,但是要从中得到任何东西,我需要能够以这种特定顺序访问下一个Number,但是我没有知道如何处理给定的数据类型。 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. 我假设您希望避免使用内置算术-否则,因为您观察到可以将其转换为内置数字,执行算术并转换回去,但是从“学习Haskell”的角度来看,这并不是很令人满意。

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: 例如,一种简单的解决方法是列出所有输入对Number以及它们的总和:

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. 如果还派生一个Enum实例,则可以使用fromEnum :: Enum a => a -> Int toEnum :: Enum a => Int -> a fromEnum :: Enum a => a -> InttoEnum :: Enum a => Int -> a在数据类型和与它们在数据定义中位置相对应的整数之间进行转换。 Since you have Zero in the 0th position, and so on, this will be exactly what you want for going to and from integers. 既然你有Zero在第0位置,等等,这将是正是你想要什么要和整数。

But you should also consider: what will be the result of addNumber Three Two ? 但是您还应该考虑: addNumber Three Two的结果是什么? There's no correct value there, because your definition of numbers doesn't go up to Five. 那里没有正确的值,因为您对数字的定义不会达到5。 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. 如果您只是对数据类型进行练习,那么这可能对您来说很好,但是通常在Haskell程序中,我们会尽量避免使用部分函数,​​因为在“应该”在编译时被捕获的情况下,它们可能会导致运行时错误。 You could, for example, return Maybe Number instead of Number , and then return Nothing if there's no valid answer. 例如,您可以返回Maybe Number而不是Number ,如果没有有效答案,则返回Nothing 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: 这样,如果您想直接定义加法(而不是通过标准类型Int并使用标准+的绕道而行),则必须这样做:

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 !). 详尽列出每个案例(幸运的是,由于您最多只有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. ...“ Peano”数字的递归数据类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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