GHC如何使用Num实例将多态数字文字转换为任意类型?

时间:2019-05-31 02:59:40

标签: haskell polymorphism ghc typeclass

据我所知,GHC可以将具有默认多态类型Num a => a的任何数字文字转换为具有Num实例的任何类型。我想知道这是否正确,并了解一些基本机制。

为了对此进行探讨,我编写了一个名为MySum的数据类型,该数据类型从Data.Monoid复制了Sum的功能(部分)。最重要的部分是它包含instance Num a => Num (MySum a)

注意-这恰好是我的问题开始的地方。 Monoid并不特别相关。我已经在该问题的底部添加了部分代码,以防万一对于参考内容的答案很有用。

在以下情况下,GHCi似乎很乐意遵守“ v :: MySum t”形式的输入:

  1. v是Num a => a类型的多态值

  2. t是Num

  3. 下的(可能是多态的)类型

据我所知,唯一与类型Num a => a兼容的数字文字是看起来像整数的数字文字。总是这样吗?似乎暗示一个值可以在Num下完全实例化为任何类型。如果这是正确的话,那么在给定5 :: MySum Int中的函数fromInteger的情况下,我了解类似Num的工作方式。

说了这么多,我不知道是怎么回事:

*Main Data.Monoid> 5 :: Fractional a => MySum a
MySum {getMySum = 5.0}

如果有可能以一种对新手友好的方式进行解释,我将不胜感激。


实例Num a => Num (MySum a),如所承诺的:

import Control.Applicative

newtype MySum a = MySum {getMySum :: a}
  deriving Show

instance Functor MySum where
  fmap f (MySum a) = MySum (f a)

instance Applicative MySum where
  pure = MySum
  (MySum f) <*> (MySum a) = MySum (f a)

instance Num a => Num (MySum a) where
  (+) = liftA2 (+)
  (-) = liftA2 (-)
  (*) = liftA2 (*)
  negate = fmap negate
  abs = fmap abs
  signum = fmap signum
  fromInteger = pure . fromInteger

2 个答案:

答案 0 :(得分:4)

您已经发现,整数文字5 amounts to

fromInteger 5

由于fromInteger的类型为Num a => Integer -> a,因此您可以将5实例化到您选择的Num实例,例如Int,{{1 }},Double或其他任何内容。特别是,假设MySum DoubleFractional的子类,并且您编写了一个Num实例,则Num a => Num (MySum a)可以正常工作:

5 :: Fractional a => MySum a
  

似乎暗示一个值可以在Num下完全实例化为任何类型。

这里的东西有些微妙。整数值可以转换5 :: Fractional a => MySum a fromInteger 5 :: Fractional a => MySum a (pure . fromInteger) 5 :: Fractional a => MySum a MySum (fromInteger 5 :: Fractional a => a) 下的任何类型(通过Num,通常为fromIntegral)。我们可以将fromInteger之类的整数文字实例化为5下的任何内容,因为GHC通过将其转换为Num来为我们处理转换。但是,我们无法将单态值fromInteger 5 :: Num a => a实例化为5 :: Integer,也不能将Double实例化为非5 :: Integral a => a类型,例如Integral。在这两种情况下,类型注释进一步限制了类型,因此,如果需要Double,则必须显式执行转换。

答案 1 :(得分:3)

您大体上是正确的:整数文字5fromInteger (5 :: Integer)等效,因此类型为Num a => a;浮点文字5.0等效于fromRational (5.0 :: Rational),类型为Fractional a => a。这确实可以解释5 :: MySum Int5 :: Fractional a => MySum a并不那么棘手。根据上述规则,它扩展为:

fromInteger (5 :: Integer) :: Fractional a => MySum a

fromInteger的类型为Num b => Integer -> b。因此,对于上述表达式进行类型检查,GHC必须将bMySum a统一。因此,根据Num (MySum a),GHC现在必须解决Fractional aNum (MySum a)由您的实例解决,产生约束Num aNumFractional的超类,因此对Fractional a的任何解决方案也将是Num a的解决方案。所以一切都检查完了。

不过,您可能想知道,如果5在此处通过fromInteger,为什么在GHCi中以MySum结尾的值看起来像Double?这是因为在类型检查之后,Fractional a => MySum a仍然是模棱两可的-当GHCi打印该值时,它实际上需要选择一个a才能选择合适的Fractional实例,所有。如果我们不处理数字,那么我们可能最终会在a中抱怨GHC。

但是有一个特殊情况in the Haskell standard。简短的概述是,如果您遇到上述仅涉及数字类型类的歧义问题,Haskell会明智地选择IntegerDouble作为歧义类型,并使用第一个类型检查。在这种情况下,即为Double。如果您想了解更多有关此功能的信息,this blog post会在激励和阐述标准的内容方面做得不错。