Haskell数字和类型系统? [英] Haskell numbers and type system?

查看:117
本文介绍了Haskell数字和类型系统?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这段Javascript代码:

  N1 = Math.floor(275 * month / 9)
N2 = Math.floor((month + 9)/ 12)
N3 =(1 + Math.floor((year - 4 * Math.floor(year / 4)+ 2)/ 3))
N = N1 - (N2 * N3)+ day - 30
return N

我试图将其转换成Haskell。像这样:

  day_of_year year month day = n1  - (n2 * n3)+ day  -  30 
其中
n1 =楼层(275 *从整月/ 9)
n2 =楼层(月+ 9/12)
n3 = 1 +楼层((年 - 4 *楼层(来自整年/ 4)+ 2)/ 3)

不起作用:(

这里是我的问题:


  1. 为什么 n1 $ c> n1 ::(Integral b,RealFrac a)=> a - > b

    但不是像 n1 ::(RealFrac a ,积分b)=> a - > b

    floor ::(积分b,RealFrac a)=> a - > b


    答案: => 的左边不重要 -

    ghci通常会尽量保持顺序与声明中的顺序相同

    但有时它默认为abc订购



  2. 这是声明正确: n1 取整数并返回RealFrac。

    $ b


    答案:是的。如果我们知道在 =>

    的左侧排序不重要,那么我们也知道(积分b,RealFrac a)===(RealFrac a,积分b)< br>
    只有类型 a - > b

    或在这种情况下积分 - > RealFrac



  3. n3 有单形憔悴症。它如何被治愈?

    我比对这项工作更感兴趣。我已经读过关于单声道...但我不知道该放哪里::在这种情况下:($ / b
    $ b


    答案:这里没有单形性,看看FUZxxl的答案:)


  4. c> day_of_year 就像这样: Integral - >积分 - >积分 - >积分


    取3积分并返回积分结果。


    答:是的,它可以!它也可以是

    :: Integral a => a - > a - > a - > a

    :: Int - > Int - > Int - > - > Int >
    ::(积分a,积分a2,积分a1)=> a - > a1 - > a2 - > a2


  5. 我想 day_of_year 可能只需要3个Ints或3个整数。它不能像2 Ints 1整数那样混合。对吗?


    FUZxxl:不,它可以混合使用不同的参数类型!看看后续4 !!!


  6. 是否可以创建 day_of_year 取3个Nums并返回一个数字?


    FUZxxl:是的!在年,月和日之前放置一个fromEnum




解决方案

好。无论何时遇到类型问题,通过向编译器提供显式类型注释开始是最好的方法。由于可能不会太大,使它们成为 Int s是个好主意。

  day_of_year :: Int  - >您也显然错过了一个大括号,我为您解决了这个问题:

Int - > Int - >诠释
日年月日= n1 - (n2 * n3)+日 - 30
其中
n1 = floor(275 * from整月/ 9)
n2 = floor( (年 - 4 *楼(从整年/ 4)+ 2)/ 3)

当我尝试编译这个时,GHC会吐出这个相当冗长的错误信息:

 
bar.hs:8:16:
由于使用floor引起的(RealFrac Int)
没有实例
可能的修正:为(RealFrac Int)$添加一个实例声明
在'(+)'的第二个参数中,即
`floor((year - 4 * floor(fromIntegral year / 4)+ 2)/ 3)'
在表达式中:
1 + floor((年 - 4 * floor(从整年/ 4)+ 2)/ 3)
在'n3'的等式中:
n3 = 1 + floor((year - 4 * floor(从整年/ 4)+ 2)/ 3)

bar.hs:8:68:
没有用于实例(小数整数)
'/'
可能的解决方法:为(Fractional Int)
添加实例声明在floor的第一个参数中,即
`((年 - 4 * floor(fromIntegral year / 4)+ 2)/ 3) '
'(+)'的第二个参数,即
`floor((year - 4 * floor(fromIntegral year / 4)+ 2)/ 3)'
在表达式:
1 + floor((year-4 * floor(fromIntegral year / 4)+ 2)/ 3)

第二个错误是重要的错误,第一个错误是更多的后续。它基本上是这样说的: Int 不会实现分区而不是 floor 。在Haskell中,整数除法使用了一个不同的函数( div quot ),但您希望在这里进行浮点除法。由于 year 固定为 Int ,减数 4 * floor(从整年/ 4)+ 2 也被固定为 Int 。然后你除以3,但如前所述,你不能使用浮动分割。让我们通过在整除之前将整个术语铸造成另一种类型,其中 fromIntegral >来解决这个问题。

fromIntegral 具有签名(Integral a,Num b)=> a - > B'/ code>。这意味着: fromIntegral 接受一个整型变量(例如 Int Integer )并返回任何数字类型的变量。



让我们尝试编译更新后的代码。类似的错误出现在 n2 的定义中,我也修正了它:

  day_of_year :: Int  - > Int  - > Int  - >诠释
日年月日= n1 - (n2 * n3)+日 - 30
其中
n1 = floor(275 * from整月/ 9)
n2 = floor(从整月(+ 4)/ 2)/ 3)
= / code) pre>

这段代码编译并运行正常(在我的机器上)。 Haskell具有某些类型违约规则,导致编译器选择 Double 作为所有浮动分区的类型。



其实,你可以做得比这更好。如何使用整数除法而不是重复的浮点转换?

  day_of_year :: Int  - > Int  - > Int  - >诠释
日年月日= n1 - (n2 * n3)+日 - 30
其中
n1 = 275 *月`9
n2 =(月+ 9) ```12
n3 = 1 +(year - 4 *(year``4)+ 2)`3

这个算法应该总是产生与上面浮点数相同的结果。这可能大概快十倍。反引号允许我使用一个函数( quot )作为操作符。



关于第六点:是的,这样做很容易。只需在 year month 之前放置一个 fromEnum ,然后。来自Enum :: Enum a =>的函数 a - > Int 将任何枚举类型转换为 Int 。 Haskell中所有可用的数字类型(复杂的iirc除外)都是类 Enum 的成员。这不是一个好主意,因为你通常拥有 Int 参数和多余的函数调用会减慢你的程序。更好地转换显式,除非您的函数预计会与许多不同类型一起使用。其实,不要太担心微观优化。 ghc有一个复杂的,有点神秘的优化基础设施,使得大多数程序快速发展。

修正



后续1,2和3



是的,你的推理是正确的。

后续4



如果您未给出 day_of_year 类型签名的浮点变体,则其类型默认为 day_of_year ::(积分a,积分a2,积分a1)=> a - > a1 - > a2 - > A2 。这基本上意味着: day month year 可以是任何类型的实现 Integral typeclass。该函数返回与 day 相同类型的值。在这种情况下, a a1 a2 不同的类型变量 - 是的,Haskell在类型层次上也有变量(并且在种类层次上也是类型的类型),但这是另一个故事)。所以如果你有

pre $ day_of_year(2012 :: Int16)(5 :: Int8)(1 :: Integer)

变量 a 实例化为 Int16 a1 变成 Int8 a2 变成整数。那么在这种情况下,返回类型是什么?


它是整数



后续5


$ b $事实上,你是和不在同一时间。尽可能地将类型尽可能地变成一般的类型有它的优点,但是它会混淆类型检查器,因为当没有显式类型注释的术语类型过于笼统时,编译器可能会发现不止一种可能键入一个术语。这可能会导致编译器选择某种类型的标准,虽然有些不直观的规则,或者它只是用一个奇怪的错误迎接你。



如果你确实需要一个通用类型,争取像

  day_of_year :: Integral a => a  - > a  - > a  - > a 

即:参数可以是任意的 Integral type,但所有参数必须具有相同的类型。



请记住,Haskell 永远不会投射类型。当涉及(自动)投射时,几乎不可能完全推断类型。您只能手动投射。有些人现在可能会在模块 Unsafe.Coerce 中告诉您有关 unsafeCoerce 的函数,该函数的类型为 a - > b ,但你实际上不想知道。它可能不会做你认为它做的事。



后续行动6



没有错与 div 。当负数涉及时,差异开始出现。现代处理器(如Intel,AMD和ARM制造的)在硬件中实现 quot rem div 也使用这些操作,但是会做一些调整以获得不同的行为。当你不真正依赖关于负数的确切行为时,这会不必要地减慢计算速度。 (实际上有几台机器在硬件中实现 div 但不是 quot ,这是我现在唯一能记住的一个 mmix 虽然)


I have this piece of Javascript code:

N1 = Math.floor(275 * month / 9)
N2 = Math.floor((month + 9) / 12)
N3 = (1 + Math.floor((year - 4 * Math.floor(year / 4) + 2) / 3))
N = N1 - (N2 * N3) + day - 30
return N

I tried to port that into a Haskell. Like this:

day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor( month + 9 / 12)
    n3 =  1 +  floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)

It doesn't work :(
Here are my questions:

  1. Why is n1 type written like n1 :: (Integral b, RealFrac a) => a -> b
    but not like n1 :: (RealFrac a, Integral b) => a -> b
    It's the same with floor :: (Integral b, RealFrac a) => a -> b

    Answer: the order is unimportant at left side of =>
    ghci will generally try to keep the order the same as the order in the declaration
    but sometimes it defaults to abc ordering

  2. Is this statement correct: n1 takes Integral number and returns RealFrac.

    Answer: Yes. If we know that ordering is unimportant at left side of =>
    then we also know that (Integral b, RealFrac a) === (RealFrac a, Integral b)
    what only matters are types a -> b
    or in this case Integral -> RealFrac

  3. n3 have Monomorphism sickness. How can it be cured?
    I am more interested in big picture of than just making this f work. I have read about mono... but I have no idea where to put :: in this case :(

    Answer: No monomorphism here. Look at FUZxxl's answer :)

  4. Can day_of_year be like this: Integral -> Integral -> Integral -> Integral?
    Takes 3 Integrals and return Integral result.

    Answer: Yes it can! It can also be
    :: Integral a => a -> a -> a -> a
    :: Int -> Int -> Int -> -> Int
    :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2

  5. I suppose day_of_year could take only 3 Ints or 3 Integers. It could not take a mix like 2 Ints 1 integer. Right?

    FUZxxl: No, it can take a mix of different argument types! Look at Follow up 4!!!

  6. Is it possible to create day_of_year to take 3 Nums and return a Num?

    FUZxxl: Yes it is! Put a fromEnum in front of year, month and day

解决方案

Okay. Whenever you have type-problems, it's the best way to start by giving explicit type annotations to the compiler. Since day, month and year are probably not too big, it's a good idea to make them Ints. You also apparently missed a brace, I fixed that for you:

day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor((month + 9) / 12)
    n3 =  1 +  floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)

When I try to compile this, GHC spits out this rather lengthy error message:

bar.hs:8:16:
    No instance for (RealFrac Int)
      arising from a use of `floor'
    Possible fix: add an instance declaration for (RealFrac Int)
    In the second argument of `(+)', namely
      `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
    In the expression:
      1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)
    In an equation for `n3':
        n3 = 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)

bar.hs:8:68:
    No instance for (Fractional Int)
      arising from a use of `/'
    Possible fix: add an instance declaration for (Fractional Int)
    In the first argument of `floor', namely
      `((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
    In the second argument of `(+)', namely
      `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
    In the expression:
      1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)

The second error is the important error, the first one is more a follow-up. It essentially says: Int does not implement division not floor. In Haskell, integral division uses a different function (div or quot), but you want floating division here. Since year is pinned to be an Int, the subtrahend 4 * floor(fromIntegral year / 4) + 2 is also pinned to be an Int. Then you divide by 3, but as said before, you can't use a floating division. Let's fix that by 'casting' the whole term to another type with fromIntegral before dividing (as you did before).

fromIntegral has the signature (Integral a, Num b) => a -> b. This means: fromIntegral takes a variable of an integral type (such as Int or Integer) and returns a variable of any numeric type.

Let's try to compile the updated code. A similar error appears in the defintion of n2, I fixed it as well:

day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor((fromIntegral month + 9) / 12)
    n3 =  1 +  floor(fromIntegral (year - 4 * floor(fromIntegral year / 4) + 2) / 3)

This code compiles and runs fine (on my machine). Haskell has certain type-defaulting rules, causing the compiler to pick Double as the type for all floating divisions.

Actually, you can do better than that. How about using integer division instead of repeated float-point conversions?

day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = 275 * month `quot` 9
    n2 = (month + 9) `quot` 12
    n3 = 1 + (year - 4 * (year `quot` 4) + 2) `quot` 3

This algorithm should always yield the same result as the floating point version above. It's just probably about ten times faster. The backticks allow me to use a function (quot) as an operator.

About your sixth point: Yes, it would be pretty easy to do that. Just put a fromEnum in front of year, month and day. The function fromEnum :: Enum a => a -> Int converts any enumeration type to an Int. All available numeric types in Haskell (except the complex ones iirc) are member of the class Enum. It's not a very good idea though, dince you usually have Int arguments and superfluous function calls can slow down your program. Better convert explicitly, except if your function is expected to be used with many different types. Actually, don't worry about micro-optimizations too much. ghc has a complicated and somewhat arcane optimization infrastructure that makes most programs blazing fast.

Amendment

Follow-up 1, 2 and 3

Yes, your reasoning is about right.

Follow-up 4

If you don't give the floating-point variant of day_of_year a type-signature, its type defaults to day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2. This essentially means: day, month and year can be of an arbitrary type that implements the Integral typeclass. The function returns a value of the same type as day. In this case, a, a1 and a2 are just different type variables - yes, Haskell also has variables on type level (and also on kind level [which is the type of a type], but that's another story) - that can be satisfied with any type. So if you have

day_of_year (2012 :: Int16) (5 :: Int8) (1 :: Integer)

The variable a gets instaniated to Int16, a1 becomes Int8 and a2 becomes Integer. So what's the return-type in this case?

It's Integer, have a look at the type-signature!

Follow-up 5

In fact, you are and aren't at the same time. Making the type as general as possible certainly has its advantages, but at the it confuses the typechecker, because When the types involved in a term without an explicit type-annotation are too general, the compiler may find out that there is more than one possible type for a term. This may either cause the compiler to pick a type by some standardized albeit somewhat unintuitive rules, or it simply greets you with a strange error.

If you really need a general type, strive for something like

day_of_year :: Integral a => a -> a -> a -> a

That is: arguments may be of arbitrary Integral type, but all arguments must have the same type.

Always remember that Haskell never casts types. It's almost impossible to infer types completely when there is (automatic) casting involved. You only cast manually. Some people might now tell you about the function unsafeCoerce in the module Unsafe.Coerce, which has the type a -> b, but you actually don't want to know. It probably doesn't do what you think it does.

Follow-up 6

There is nothing wrong with div. The difference starts to appear when negative numbers are involved. Modern processors (like those made by Intel, AMD and ARM) implement quot and rem in hardware. div also uses these operations but does some twiddling to get a different behavior. That unneccessarily slows down computations when you don't really depend on the exact behavior regarding negative numbers. (There are actually a few machines that implement div but not quot in hardware. The only one I can remember right now is though)

这篇关于Haskell数字和类型系统?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆