Difference between -> and => symbols. What do they mean?

Viewed 2159

In Haskell, when we talk type declaration.

I've seen both -> and =>.

As an example: I can make my own type declaration.

addMe :: Int -> Int -> Int
addMe x y = x + y

And it works just fine.

But if we take a look at :t sqrt we get:

sqrt :: Floating a => a -> a

At what point do we use => and when do we use ->? When do we use "fat arrow" and when do we use "thin arrow"?

3 Answers

-> is for explicit functions. I.e. when f is something that can be written in an expression of the form f x, the signature must have one of these arrows in it†. Specifically, the type of x (the argument) must appear to the left of a -> arrow.

It's best to not think of => as a function arrow at all, at least at first‡. It's an implication arrow in the logical sense: if a is a type with the property Floating a, then it follows that the signature of sqrt is a -> a.

For your addMe example, which is a function with two arguments, the signature must always have the form x -> y -> z. Possibly there can also be a q => in front of that; that doesn't influence the function-ishness, but may have some saying in what particular types are allowed. Generally, such constraints are not needed if the types are already fixed and concrete. Like, you could in principle impose a constraint on Int:

addMe :: Num Int => Int -> Int -> Int
addMe x y = x + y

...but that doesn't really accomplish anything, because everybody knows that the particular type Int is an instance of the Num class. Where you need such constraints is when the type is not fixed but a type variable (i.e. lowercase), i.e. if the function is polymorphic. You can't just write

addMe' :: a -> a -> a
addMe' x y = x + y

because that signature would suggest the function works for any type a whatsoever, but it can't work for all types (how would you add, for example, two strings? ok perhaps not the best example, but how would you multiply two strings?)

Hence you need the constraint

addMe' :: Num a => a -> a -> a
addMe' x y = x + y

This means, you don't care what exact type a is, but you do require it to be a numerical type. Anybody can use the function with their own type MyNumType, but they need to ensure that Num MyNumType is fulfilled: then it follows that addMe' can have signature MyNumType -> MyNumType -> MyNumType.

The way to ensure this is to either use a standard type which you know to be numerical, for instance addMe' 5.9 3.7 :: Double would work, or give an instance declaration for your custom type and the Num class. Only do the latter if you're sure it's a good idea; usually the standard num types are all you'll need.


†Note that the arrow may not be visible in the signature: it's possible to have a type synonym for a function type, for example when type IntEndofunc = Int -> Int, then f :: IntEndofunc; f x = x+x is ok. But you can think of the typedef as essentially just a syntactic wrapper; it's still the same type and does have the arrow in it.

‡It so happens that logical implication and function application can be seen as two aspects of the same mathematical concept. Furthermore, GHC actually implements class constraints as function arguments, so-called dictionaries. But all this happens behind the scenes, so if anything they're implicit functions. In standard Haskell, you will never see the LHS of a => type as the type of some actual argument the function is applied to.

The "thin arrow" is used for function types (t1 -> t2 being the type of a function that takes a value of type t1 and produces a value of type t2).

The "fat arrow" is used for type constraints. It separates the list of type constraints on a polymorphic function from the rest of the type. So given Floating a => a -> a, we have the function type a -> a, the type of a function that can take arguments of any type a and produces a result of that same type, with the added constraint Floating a, meaning that the function can in fact only be used with types that implement the Floating type class.

the -> is the constructor of functions and the => is used to constraints, a sort of "interface" in Haskell called typeclass.

A little example:

sum :: Int -> Int -> Int
sum x y = x + y

that function only allows Int types, but if you want a huge int or a small int, you probably want Integer, and how to tell it to use both?

sum2 :: Integral a => a -> a -> a
sum2 x y = x + y

now if you try to do:

sum2 3 1.5

it will give you an error

also, you may want to know if two data are equals, you want:

equals :: Eq a => a -> a -> Bool
equals x y = x == y

now if you do:

3 == 4

that's ok

but if you create:

data T = A | B

equals A B

it will give to you:

error:
    • No instance for (Eq T) arising from a use of ‘equals’
    • In the expression: equals A B
      In an equation for ‘it’: it = equals A B

if you want for that to work, you must just do:

data T = A | B deriving Eq

equals A B

False
Related