繁体   English   中英

Haskell数字和类型系统?

[英]Haskell numbers and type system?

我有这段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 - > a
    :: Int - > 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放在年,月,日之前

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

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 )作为运算符。

关于你的第六点:是的,这样做很容易。 只需在yearmonthday前面放一个fromEnum 函数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被instaniated来Int16a1成为Int8a2变成Integer 那么这种情况下的返回类型是什么?

这是Integer ,看看类型签名!

后续行动5

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

如果你真的需要一般类型,那就争取类似的东西

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

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

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

后续行动6

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

我有太多的后续问题需要简单的评论。

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

    是。

  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 AND /不允许使用Integral
    • n3 ,第二year变量:通过使用floor(fromIntegral year / 4)来自整floor(fromIntegral year / 4)我真实地做出了floor可以使用的结果。 那个表达完整
      (year - 4 * floor(fromIntegral year / 4) + 2)Integral (year - 4 * floor(fromIntegral year / 4) + 2) Integral类别!
      这使得/3floor无法工作。
      我的逻辑好吗?

      是。

  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 ++中有模板,因此泛型函数适用于许多类型。

      看看后续行动5

  6. 你为什么用quot而不是div
    昨天我完全尝试了你用div做的事情, ghci 奖励我:
    No instance for (Integral (Car -> Int)) arising from a use of div

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

      我不小心从功能定义中删除了一年一个月
      day_of_year day = ...
      那个错误出现了。

ps Google: 找不到haskell的结果“(Integral(Car - > Int))”

即使谷歌也找不到我的错别字;)))

暂无
暂无

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

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