简体   繁体   English

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

[英]How to comfortably deal with the type system on Haskell?

Haskell's type system is powerful and liked for its mathematical rigorousness and logical soundness, on the other side something as naive as below makes me wonder why it doesn't work as expected by intuition? Haskell的类型系统功能强大,并且因其数学严谨性和逻辑性而受到欢迎,另一方面,如下所述的天真使我想知道为什么它不能像直觉那样工作?

Eg why can't Int be converted to Num on x3 but f1 accept Int against the signature Num ? 例如,为什么Int不能在x3上转换为Num但是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’

I knew that one should finally learn the underlying theory eg HM type system to comfortably deal with the type system, and even found some nice writings eg 1 , 2 , 3 and 4 for demystifying it. 我知道一个人应该终于学会了基本的理论如HM型系统到舒适与类型系统处理,甚至还发现了一些不错的作品,例如1234的神秘化了。 What else would you like to recommend if you ever came cross and overcame this challenge? 如果你遇到过这个挑战,你还想推荐什么呢?

@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’

First, x6 must have been a supertype of Num that is Num itself when x6 is type annotated with Num . 首先, x6一定是一个超NumNum本身时, x6的类型是带有加注解的Num However, the concatenate type annotations for Num after Int of (x6::Int)::Num a => a won't be united if we then downcast x6 from Num to Int . 然而,串连型注解NumInt(x6::Int)::Num a => a将不被如果我们然后低垂团结x6NumInt Hence, the first inferred type Num of x6 is unsatisfied here. 因此,这里不满足x6的第一个推断类型Num

why can't Int be converted to Num on x3 为什么Int不能在x3上转换为Num

  1. Int cannot be converted to Num because Int is a type and Num is a type class . Int不能转换为Num因为Int是一个类型而Num是一个类型类 The difference between these two kinds of entities will hopefully become clear in what follows. 这两种实体之间的差异有望在下文中变得清晰。

  2. Int cannot be converted to anything else because Haskell has no conversions in the sense you are using here. Int无法转换为其他任何内容,因为Haskell在您使用的意义上没有转换。 There are no implicit casts. 没有隐式演员表。 What does happen is a polymorphic type getting specialised to some definite type; 发生了什么是多态类型专门针对某些确定类型; however, a definite type never becomes something else automatically. 然而,一个明确的类型永远不会自动成为别的东西

With that in mind, let's consider your examples. 考虑到这一点,让我们考虑一下你的例子。

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

x1 here is polymorphic , which means it can assume different types depending on how you use it. x1这里是多态的 ,这意味着它可以根据你的使用方式采用不同的类型。 This indeterminacy can be recognised by the presence of the type variable a (type variables, unlike concrete types, are not capitalised). 这种不确定性可以通过类型变量a的存在来识别(类型变量,与具体类型不同,不是大写的)。 The type of x1 , though polymorphic, is to some extent restricted by the constraint Num a . x1的类型虽然是多态的,但在某种程度上受约束 Num a限制 Num a => a can be read as "any type that has an instance of the type class Num ", while a plain a would mean "any type whatsoever". Num a => a可以读作“具有类型 Num的实例的任何类型”,而简单的a将表示“任何类型的任何类型”。

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

Introducing the type annotation :: Int means requesting Int to be unified with the type of 1 , Num a => a . 引入类型注释:: Int意味着请求Int与类型1 统一Num a => a In this case, that simply means replacing the type variable a with Int . 在这种情况下,这只是意味着用Int替换类型变量a Given that Int does have an instance of Num , that is a valid move, and the type checker happily accepts it. 鉴于Int确实有一个Num实例,这是一个有效的移动,并且类型检查器很乐意接受它。 The type annotation specialises the polymorphic type of 1 to Int . 类型注释专用1Int的多态类型。

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

The type of 1 :: Int is Int . 1 :: Int的类型是Int The second annotation demands unifying it with Num a => a . 第二个注释要求用Num a => a统一它。 That, however, is impossible. 然而,这是不可能的。 Once a type is specialised, you can't "forget" the type and revert the specialisation just by providing a type annotation. 一旦类型被专门化,您就不能“忘记”类型并仅通过提供类型注释来恢复特化。 Perhaps you are thinking of OOP upcasting; 也许你正在考虑OOP向上倾斜; this is not at all the same thing. 这根本不是一回事。 By the way, if the type checker accepted x3 , you would be able to write x4 = ((1 :: Int) :: Num a => a) :: Double , thus converting an Int to a Double . 顺便说一句,如果类型检查器接受x3 ,你将能够写入x4 = ((1 :: Int) :: Num a => a) :: Double ,从而将Int转换为Double In the general case, though, there is no way this conversion can happen like that, as you didn't tell how the conversion is to be done; 但是,在一般情况下,这种转换无法像这样发生,因为您没有告诉转换是如何完成的; as for the special cases, there aren't any. 至于特殊情况,没有任何。 (Converting an Int to a Double is certainly possible, but it requires an appropriate function. For instance, you may find it relevant to consider how the type of fromIntegral relates to what it does.) (将Int转换为Double当然是可能的,但它需要一个适当的函数。例如,您可能会发现它与考虑fromIntegral的类型与它的作用有关。)

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

The principles here remain the same. 这里的原则保持不变。 The only difference is that you have to consider how the types of argument and result are related to each other. 唯一的区别是你必须考虑参数类型和结果如何相互关联。 The type of id is a -> a . id的类型是a -> a It specialises just fine to Num a => a -> a . 它特别适合Num a => a -> a Passing an Int argument further specialises it to Int -> Int , and so you get a result of type Int . 传递Int参数进一步将其专门化为Int -> Int ,因此您得到Int类型的结果。

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

f1 had a polymorphic type that you specialised by feeding it an Int argument, while f2 has a monomorphic type, and so there is no need to specialise it. f1有一个多态类型,你通过提供一个Int参数来专门化,而f2有一个单态类型,因此不需要专门化它。 id is specialised from a -> a directly to Int -> Int , while 1 is specialised from Num a => a to Int because you are feeding it to a function that expects an Int argument. 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’

Here, you want to unify a -> a , the type of id , with Num a => a -> Int . 在这里,你想统一a -> aid的类型,用Num a => a -> Int However, if you replace a with, for instance, Double in Num a => a -> Int , you get Double -> Int , which can't possibly unify with a -> a , because it changes types while a -> a doesn't. 但是,如果您更换a同,例如, DoubleNum a => a -> Int ,你会得到Double -> Int ,它不可能用统一a -> a ,因为它改变类型,同时a -> a没有。 (That is the point of Thomas M. DuBuisson's comment above: the type of your implementations is not compatible with that of id , because id can't change the type of anything.) (这是Thomas M. DuBuisson上面评论的观点:你的实现类型与id的类型不兼容,因为id不能改变任何类型。)

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

Finally, this is just like f3 , except that the mismatch happens on the result type rather than on that of the argument. 最后,这与f3类似,只是不匹配发生在结果类型而不是参数的不匹配上。 Putting a different spin on it this time, you can't implement a Num a => Int -> a function by settling on a specific type with a Num instance (be it Int , Double , etc.) and then "upcasting" it to Num a => a , because there is no such thing as upcasting. 这次对它进行不同的旋转,你不能通过使用Num实例(无论是IntDouble等)建立一个特定的类型来实现Num a => Int -> a函数然后“向上转换”它到Num a => a ,因为没有上升的东西。 Rather Num a => Int -> a must work for any choice of a whatsoever that has an instance of Num . 相反Num a => Int -> a必须努力的任何选择a具有实例任何Num

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

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