Haskell数字和类型系统?

时间:2012-06-27 17:28:49

标签: haskell types numbers

我有这段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
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor( month + 9 / 12)
    n3 =  1 +  floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)

不起作用:(
 以下是我的问题:

  1. 为什么n1类型的编写方式与n1 :: (Integral b, RealFrac a) => a -> b类似 但不像n1 :: (RealFrac a, Integral b) => a -> b
    它与floor :: (Integral b, RealFrac a) => a -> b

    相同
      

    答案: => 左侧的订单不重要  ghci通常会尝试保持订单与声明中的订单相同  但有时它默认为abc命令

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

      

    答案:是的。如果我们知道在 => 的左侧订购并不重要  那么我们也知道(Integral b,RealFrac a)===(RealFrac a,Integral b)
     唯一重要的是类型 a - > B'/强>
     或者在这种情况下积分 - > RealFrac

  3. n3有单态性疾病。如何治愈?
    我更感兴趣的是大局而不仅仅是让这个工作。我读过关于单声道......但我不知道放在哪里::在这种情况下:(

      

    答案:这里没有单态。看看FUZxxl的答案:)

  4. day_of_year可以这样:Integral -> Integral -> Integral -> Integral吗? 取3个积分并返回积分结果。

      

    答案:是的,可以!它也可以是
     ::积分a => a - > a - > a - >一个
     :: Int - > Int - > Int - > - >诠释
     ::(积分a,积分a2,积分a1)=> a - > a1 - > a2 - > A2

  5. 我认为day_of_year只能占用3个Int或3个整数。它不能像2 Ints 1整数那样混合。正确?

      

    FUZxxl:不,它可以采用不同的参数类型!看看跟进4 !!!

  6. 是否可以创建day_of_year以获得3个Nums并返回Num?

      

    FUZxxl:是的!将fromEnum放在年,月,日前面

2 个答案:

答案 0 :(得分:16)

好。每当遇到类型问题时,最好的方法是先向编译器提供显式类型注释。由于daymonthyear可能不是太大,因此最好将它们Int设为{1}}。你也显然错过了一个支架,我为你解决了这个问题:

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)

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

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)

第二个错误是重要错误,第一个错误是后续问题。它基本上说:Int没有实现除floor之类的除法。在Haskell中,积分除法使用不同的函数(divquot),但是你想要浮动除法。由于year被固定为Int,因此减数4 * floor(fromIntegral year / 4) + 2也固定为Int。然后除以3,但如前所述,你不能使用浮动除法。让我们通过在分割之前将整个术语“转换”为fromIntegral的另一种类型来解决这个问题(就像之前一样)。

fromIntegral具有签名(Integral a, Num b) => a -> b。这意味着:fromIntegral采用整数类型的变量(例如IntInteger)并返回任何数字类型的变量。

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

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)

此代码编译并运行正常(在我的机器上)。 Haskell具有某些类型默认规则,导致编译器选择Double作为所有浮动除法的类型。

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

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

此算法应始终产生与上述浮点版本相同的结果。它可能快了大约十倍。反引号允许我使用函数(quot)作为运算符。

关于你的第六点:是的,这样做很容易。只需将fromEnum放在yearmonthday之前。函数fromEnum :: Enum a => a -> Int将任何枚举类型转换为Int。 Haskell中的所有可用数值类型(复杂的iirc除外)都是类Enum的成员。这不是一个好主意,但是你通常会有Int个参数,多余的函数调用可能会减慢你的程序。明确更好地转换,除非您的函数预期与许多不同类型一起使用。实际上,不要过分担心微观优化。 ghc有一个复杂的,有些神秘的优化基础设施,使大多数程序都能快速发展。

修订

后续1,2和3

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

后续行动4

如果不为day_of_year提供类型签名的浮点变体,则其类型默认为day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2。这实际上意味着:daymonthyear可以是实现Integral类型类的任意类型。该函数返回与day相同类型的值。在这种情况下,aa1a2只是不同的类型变量 - 是的,Haskell在类型级别上也有变量(也在类型级别上[这是一种类型的类型,但这是另一个故事) - 任何类型都可以满足。所以,如果你有

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

变量a变为Int16a1变为Int8a2变为Integer。那么这种情况下的返回类型是什么?

  

它是Integer,看一下类型签名!

后续行动5

事实上,你现在和不在同一时间。使类型尽可能通用当然有其优点,但是它会混淆类型检查器,因为当没有显式类型注释的术语中涉及的类型过于笼统时,编译器可能会发现存在多个可能的类型输入一个术语。这可能会导致编译器通过一些标准化的选择类型,虽然有点不直观的规则,或者只是用一个奇怪的错误问候你。

如果你真的需要一般类型,请努力寻找像

这样的东西
day_of_year :: Integral a => a -> a -> a -> a

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

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

后续行动6

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

答案 1 :(得分:0)

对于一个简单的评论,我有太多的跟进问题。

  1. n1很明显。 fromIntegral需要month并将其转换为/的某种类型的必需品 我在这儿吗?

      

    是的。

  2. 但在n2我们可以假设为 fromIntegral(month + 9) === (fromIntegral month + 9)

    • 在第一种情况下添加month9,然后将其转换为/的某种类型 这是有效的,因为+位于Num,因此每个数字都可以+而无需投放。 像1,2,3这样的原始数字也是Num类型。
    • 第二种情况有某种“延迟铸造”。 (fromIntegral month + 9)类型为Num a => a,但由于/12编译器强制转换month9属于与/兼容的某种类型。
      我得到了这个吗?

        

      !是

    •   

  3. FUZxxl,伙计,谢谢! 通过修补代码并随意放置fromIntegral,我非常接近解决这个问题 但是让代码工作与知道我们为什么要做某事并不相同!

    • floor不允许/Integral
    • n3,第二个year变量: 使用floor(fromIntegral year / 4)我真实地做出了floor可以使用的结果。 而那个表达完整了 (year - 4 * floor(fromIntegral year / 4) + 2)一个Integral类型类! 因此,/3和第一floor无法工作 我的逻辑是否正常?

        

      !是

    •   

  4. 您的输入有效:day_of_year :: Int -> Int -> Int -> Int
    我的输入也有效:day_of_year :: Integral a => a -> a -> a -> a
    自动输入:day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2
    这是什么意思?什么是a1,a2?为什么a,a1,a2?

      

    后续行动4中的真棒答案

  5. 我在这里试图创建一个需要Integral而不是特定IntInteger的一般函数?

    • 在JavaScript中,所有内容都是自动排序/动态输入/类型Number
    • 在C ++中有templates因此泛型函数适用于许多类型。

        

      !查看后续行动5

    •   

  6. 为什么使用quot代替div
    昨天我尝试使用divghci 奖励我所做的完全相同的事情:
    No instance for (Integral (Car -> Int)) arising from a use of div

    • 这是什么错误?
    • 在这种情况下,div和quot之间的区别是什么?

        

      !我不小心从功能定义中删除了   ! day_of_year day = ...
        !并且出现了错误。

    •   

  7. P.S。 Google:没有找到针对haskell的结果“(Integral(Car - > Int))”

      

    即使谷歌找不到我的拼写错误;)))