如何在Haskell上轻松处理类型系统?

时间:2016-11-04 22:41:03

标签: haskell polymorphism type-inference type-systems

Haskell的类型系统功能强大,并且因其数学严谨性和逻辑性而受到欢迎,另一方面,如下所述的天真使我想知道为什么它不能像直觉那样工作?

E.g。为什么Int无法将Num转换为x3,而f1接受Int签名Num

Prelude> let x1 = 1
Prelude> :t x1
x1 :: Num a => a

Prelude> let x2 = 1 :: Int
Prelude> :t x2
x2 :: Int

Prelude> let x3 = (1 :: Int) :: Num a => a
Couldn't match expected type ‘a1’ with actual type ‘Int’

Prelude> let f1 :: Num a => a -> a; f1 = id
Prelude> :t f1 (1 :: Int)
f1 (1 :: Int) :: Int

Prelude> let f2 :: Int -> Int; f2 = id
Prelude> :t f2 1
f2 1 :: Int

Prelude> let f3 :: Num a => a -> Int; f3 = id

Prelude> let f4 :: Num a => Int -> a; f4 = id
Couldn't match type ‘a’ with ‘Int’

我知道最终应该学习基础理论,例如HM type system轻松处理类型系统,甚至发现了一些不错的着作,例如揭穿它的1234。如果你遇到过这个挑战,你还想推荐什么呢?

@EDIT

Prelude> let f5 x5 = x5::Int
Prelude> :t f5
f5 :: Int -> Int

Prelude> let f6 x6 = x6::Num a => a
Couldn't match expected type ‘a1’ with actual type ‘t’

首先,当x6使用Num进行注释时,Num必须是x6的超类型Num。但是,如果我们将NumInt(x6::Int)::Num a => a转发为x6,则Num Int之后的Num的连结类型注释将无法统一。因此, x6的第一个推断类型!(n & 1)在这里不满意。

1 个答案:

答案 0 :(得分:17)

  

为什么Int

上无法将Num转换为x3
  1. Int无法转换为Num,因为Int是一种类型而Num类型。这两种实体之间的差异有望在以下内容中得到明确。

  2. Int无法转换为其他任何内容,因为Haskell在您使用的意义上没有转换。没有隐式演员表。如果将专用变为某种确定类型的多态类型,会发生什么?但是,明确的类型永远不会自动成为别的东西。

  3. 考虑到这一点,让我们考虑你的例子。

    Prelude> let x1 = 1
    Prelude> :t x1
    x1 :: Num a => a
    

    x1这里是多态,这意味着它可以根据您的使用方式采用不同的类型。这种不确定性可以通过类型变量a的存在来识别(类型变量,与具体类型不同,不是大写的)。 x1的类型虽然是多态的,但在某种程度上受约束 Num a的限制。 Num a => a可以理解为“具有类型类 Num的实例的任何类型”,而普通a则表示“任何类型”。

    Prelude> let x2 = 1 :: Int
    Prelude> :t x2
    x2 :: Int
    

    引入类型注释:: Int表示请求Int 统一,其类型为1Num a => a。在这种情况下,这只是意味着用a替换类型变量Int。鉴于Int确实有Num的实例,这是一个有效的移动,并且类型检查器乐意接受它。类型注释专门 1Int的多态类型。

    Prelude> let x3 = (1 :: Int) :: Num a => a
    Couldn't match expected type ‘a1’ with actual type ‘Int’
    

    1 :: Int的类型为Int。第二个注释要求将其与Num a => a统一起来。然而,这是不可能的。一旦类型被专门化,您就不能“忘记”类型并仅通过提供类型注释来恢复特化。也许你正在考虑OOP向上倾斜;这根本不是一回事。顺便说一下,如果类型检查器已接受x3,您就可以编写x4 = ((1 :: Int) :: Num a => a) :: Double,从而将Int转换为Double。但是在一般情况下,这种转换无法像这样发生,因为你没有告诉如何进行转换;至于特殊情况,没有任何。 (将Int转换为Double当然是可能的,但它需要适当的功能。例如,您可能会发现考虑fromIntegral的类型与其所涉及的相关性是相关的。)

    Prelude> let f1 :: Num a => a -> a; f1 = id
    Prelude> :t f1 (1 :: Int)
    f1 (1 :: Int) :: Int
    

    这里的原则保持不变。唯一的区别是你必须考虑参数类型和结果如何相互关联。 id的类型为a -> a。它专门适用于Num a => a -> a。传递Int参数会进一步将其专门化为Int -> Int,因此您会得到Int类型的结果。

    Prelude> let f2 :: Int -> Int; f2 = id
    Prelude> :t f2 1
    f2 1 :: Int
    

    f1有一个多态类型,你通过提供Int参数来专门化,而f2具有单态类型,因此不需要专门化它。 id专门从a -> a直接发送到Int -> Int,而1专门从Num a => aInt,因为您正在将其提供给一个函数期待Int参数。

    Prelude> let f3 :: Num a => a -> Int; f3 = id
    Couldn't match type ‘a’ with ‘Int’
    

    在此,您希望将a -> a的类型idNum a => a -> Int统一起来。但是,如果您使用a中的Double替换Num a => a -> Int,则会获得Double -> Int,这不可能与a -> a统一,因为它更改类型而a -> a没有。 (这是Thomas M. DuBuisson上面评论的要点:你的实现类型与id的类型不兼容,因为id不能改变任何类型。)

    Prelude> let f4 :: Num a => Int -> a; f4 = id
    Couldn't match type ‘a’ with ‘Int’
    

    最后,这就像f3,除了不匹配发生在结果类型而不是参数的类型上。这一次采用了不同的旋转方式,您无法通过使用Num a => Int -> a实例(NumInt来确定特定类型来实现Double功能,等)然后“向上转发”它到Num a => a,因为没有上升的东西。相反,Num a => Int -> a必须适用于具有a实例的任何选择的Num