In haskell, why do I need to specify type constraints, why can't the compiler understand them?

Consider the function

add ab = a + b 

It works:

 *Main> add 1 2 3 

However, if I add a type signature, indicating that I want to add things of the same type:

 add :: a -> a -> a add ab = a + b 

I get an error message:

 test.hs:3:10: Could not deduce (Num a) from the context () arising from a use of `+' at test.hs:3:10-14 Possible fix: add (Num a) to the context of the type signature for `add' In the expression: a + b In the definition of `add': add ab = a + b 

This way, GHC can explicitly infer that I need a type restriction of Num , as it just told me:

 add :: Num a => a -> a -> a add ab = a + b 

Works.

Why does the GHC require me to add a type constraint? If I do general programming, why can't it work only on everything that the + operator can use?

In C ++ template programming, you can do this easily:

 #include <string> #include <cstdio> using namespace std; template<typename T> T add(T a, T b) { return a + b; } int main() { printf("%d, %f, %s\n", add(1, 2), add(1.0, 3.4), add(string("foo"), string("bar")).c_str()); return 0; } 

The compiler calculates add argument types and generates a version of the function for this type. There seems to be a fundamental difference in the Haskell approach, can you describe it and discuss trade-offs? It seems to me that this would be allowed if the GHC just filled in the type restriction for me, as it obviously decided that it was necessary. However, why a type restriction at all? Why not just compile it successfully while the function is used only in a valid context where the arguments are in Num ?

+6
type-inference type-systems haskell typeclass
source share
4 answers

If you do not want to specify the type of function, just leave it and the compiler will automatically select the types. But if you decide to specify types, they must be correct and accurate.

+14
source share

The whole point of types is to have a formal way of declaring the right and wrong way to use a function. The type (Num a) => a -> a -> a describes exactly what is required of the arguments. If you miss a class constraint, you will have a more general function that can be used (erroneously) in more places.

And this not only does not allow you to pass Num values ​​to add . Everywhere the function goes, the type will definitely go. Consider this:

 add :: a -> a -> a add ab = a + b foo :: [a -> a -> a] foo = [add] value :: [String] value = [f "hello" "world" | f <- foo] 

You want the compiler to reject this, right? How does it do it? By adding class constraints and checking that they are not deleted, even if you do not call this function directly.

What is the difference between C ++? There are no class restrictions. The compiler replaces int or std::string with T , then tries to compile the resulting code and looks for the corresponding + operator, which it can use. The template system is "weaker" because it accepts more invalid programs, and this is a sign that this is a separate stage before compilation. I would like to change C ++ to add the semantics of <? extends T> <? extends T> from Javas generics. Just study the type system and find out that parametric polymorphism is β€œstronger” than C ++ templates, namely it will reject more invalid programs.

+15
source share

I think you might be confused by the "crazy moon poetry" of the GHC error messages. He did not say that this (being a GHC) cannot deduce a restriction (Num a) . He says that the restriction (Num a) cannot be inferred from your type signature, which is known to be there due to the use of + . Therefore, you declare that this function has a type more general than the compiler knows what it can have. The compiler does not want you to lie about your functions to the world!

In the first example, which you specified without a type signature, if you run :t add in ghci, you will see that the compiler knows very well that there is a restriction (Num a) .

As for C ++ templates, remember that they are syntactic templates and only fully tested in each instance, how they are used. Your add template will work with any type, if it is used in every place, there is a suitable + operator and, possibly, conversions to make the template instance viable. There are no guarantees regarding the template until then ... therefore the body of the template should be "visible" for each module that uses it.

Basically, all C ++ can do is check the syntax of the template and then save it as a kind of very hygienic macro. While Haskell generates a real function for add (leaving aside that it can also select specialized type specializations for optimization).

+12
source share

There are times when the compiler cannot determine the right type for you and where it needs your help. Consider

 fs = show $ read s 

The compiler says:

 Ambiguous type variable `a' in the constraints: Read a' arising from a use of `read' at src\Main.hs:20:13-18 `Show a' arising from a use of `show' at src\Main.hs:20:6-9 Probable fix: add a type signature that fixes these type variable(s) 

(Oddly enough, it seems that you can define this function in ghci, but there seems to be no way to use it)

If you want something like f "1" work, you need to specify a type:

 fs = show $ (read s :: Int) 
+3
source share

All Articles