Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do I need type annotations?

Consider these functions

{-# LANGUAGE TypeFamilies #-}

tryMe :: Maybe Int -> Int -> Int
tryMe (Just a) b = a
tryMe Nothing b  = b

class Test a where
    type TT a
    doIt :: TT a -> a -> a

instance Test Int where
    type TT Int = Maybe Int
    doIt (Just a) b  = a
    doIt (Nothing) b = b

This works

main = putStrLn $ show $ tryMe (Just 2) 25

This doesn't

main = putStrLn $ show $ doIt (Just 2) 25
{- 
  • Couldn't match expected type ‘TT a0’ with actual type ‘Maybe a1’
  The type variables ‘a0’, ‘a1’ are ambiguous
-}

But then, if I specify the type for the second argument it does work

main = putStrLn $ show $ doIt (Just 2) 25::Int

The type signature for both functions seem to be the same. Why do I need to annotate the second parameter for the type class function? Also, if I annotate only the first parameter to Maybe Int it still doesn't work. Why?

like image 793
Marcelo Lazaroni Avatar asked Jul 11 '17 08:07

Marcelo Lazaroni


People also ask

Should I use Python type annotations?

Type hints work best in modern Pythons. Annotations were introduced in Python 3.0, and it's possible to use type comments in Python 2.7. Still, improvements like variable annotations and postponed evaluation of type hints mean that you'll have a better experience doing type checks using Python 3.6 or even Python 3.7.

What do type annotations do?

Type annotations — also known as type signatures — are used to indicate the datatypes of variables and input/outputs of functions and methods. In many languages, datatypes are explicitly stated. In these languages, if you don't declare your datatype — the code will not run.

What are reasons for using type hinting?

Introduction to Type Hints As the code base gets larger, type hints can help to debug and prevent some dumb mistakes. If you're using an IDE like PyCharm, you'll get a warning message whenever you've used the wrong data type, provided you're using type hints.

What advantage does using type annotations in function definitions provide?

It allows the developer to effectively communicate expected argument types and return values with the interpreter (and other developers as well) while keeping the advantages of dynamic typing.


1 Answers

All this discussion is fine, but it hasn't yet been stated explicitly that in Haskell numeric literals are polymorphic. You probably knew that, but may not have realized that it has bearing on this question. In the expression

doIt (Just 2) 25

25 does not have type Int, it has type Num a => a — that is, its type is just some numeric type, awaiting extra information to pin it down exactly. And what makes this tricky is that the specific choice might affect the type of the first argument. Thus amalloy's comment

GHC is worried that someone might define an instance Test Integer, in which case the choice of instance will be ambiguous.

When you give that information — which can come from either the argument or the result type (because of the a -> a part of doIt's signature) — by writing either of

doIt (Just 2) (25 :: Int)
doIt (Just 2) 25 :: Int   -- N.B. this annotates the type of the whole expression

then the specific instance is known.

Note that you do not need type families to produce this behavior. This is par for the course in typeclass resolution. The following code will produce the same error for the same reason.

class Foo a where
    foo :: a -> a

main = print $ foo 42

You might be wondering why this doesn't happen with something like

main = print 42

which is a good question, that leftroundabout has already addressed. It has to do with Haskell's defaulting rules, which are so specialized that I consider them little more than a hack.

like image 105
luqui Avatar answered Sep 30 '22 20:09

luqui