How to comfortably deal with the type system on Haskell?

前端 未结 1 1306
攒了一身酷
攒了一身酷 2020-12-21 09:00

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 do

相关标签:
1条回答
  • 2020-12-21 09:16

    why can't Int be converted to Num on x3

    1. Int cannot be converted to Num because Int is a type and Num is a type class. 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. 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. This indeterminacy can be recognised by the presence of the type variable a (type variables, unlike concrete types, are not capitalised). The type of x1, though polymorphic, is to some extent restricted by the constraint 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".

    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. In this case, that simply means replacing the type variable a with Int. Given that Int does have an instance of Num, that is a valid move, and the type checker happily accepts it. The type annotation specialises the polymorphic type of 1 to Int.

    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. The second annotation demands unifying it with 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; 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. 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.)

    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. It specialises just fine to Num a => a -> a. Passing an Int argument further specialises it to Int -> Int, and so you get a result of type 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. 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.

    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. 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. (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.)

    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. 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. Rather Num a => Int -> a must work for any choice of a whatsoever that has an instance of Num.

    0 讨论(0)
提交回复
热议问题