Haskell provides a lot of flexibility that other languages don't. The cost of that is that there are many situations where another language would give you a syntax or type error, but Haskell will not. Let's take your example:
3 4 :: (Num (a -> t), Num a) => t
In Haskell, if you have any two syntactically valid expressions, putting them next to each other with whitespace in between also gives you a valid expression. So for example, if f
and x
are two expressions, this is also an expression:
f x
This type of expression is called a function application, or just application for short.
Along with this syntactic rule, there's also a typing rule. I'll write it as type annotations on the previous expression:
((f :: a -> b) (x :: a)) :: b
That is, for the application to be well-typed, then for some choice of types a
and b
, f :: a -> b
, x :: a
and f x :: b
.
What type inference does it use the syntactic structure of your program and any explicit type annotations that you and the libraries you use have supplied to figure out the most general type that can be assigned to your expression.
So now, back to your example:
3 4 :: (Num (a -> t), Num a) => t
This expression is a syntactically valid application, because it has the f x
form I explained above. In addition, the integer literals 3
and 3
both have the type Num a => a
for some a
(chosen independently for each occurrence of a literal). Put all of these things together, and well, we have a syntactically well-formed expression that also typechecks if taken in isolation.
You may think it's crazy that an integer literal may in some cases be a function... but well, not so much. One example: I once had a project where I was using Haskell to prototype a domain specific language for data transformation and querying, where expressions in the DSL stood for functions from an input frame (query conditions) to values. For example, a frame might have been a set of pairs of Year/Month values and salespersons, and the value might be the amount sold by that salesperson in that month, or the number of units sold:
sales :: (YearMonth, Salesperson) -> Money
units :: (YearMonth, Salesperson) -> Integer
There's a straightfoward way of (which icktoofay's answer demonstrates) of extending the Num
class to allow us to write expressions like this:
avgSales :: (YearMonth, Salesperson) -> Money
avgSales = sales / fromIntegral units
And extending the Num
class like this also allows us to treat integer literals like 3
as literals in the DSL. The function that corresponds to 3
is the constant function that returns 3
no matter what the (YearMonth, Salesperson)
combination is:
salesTimesThree :: (YearMonth, Salesperson) -> Money
salesTimesThree = sales * 3
In practice you'd not extend Num
to the function type, however; you'd define a new type for this (and add the ability for queries to do IO to fetch data from files or databases):
newtype Query tag point value =
Query { runQuery :: Set (Tagged tag point) -> IO (Map (Tagged tag point) value) }
instance Functor (Query tag point) where ....
instance Applicative (Query tag point) where ....
instance Num value => Num (Query tag point value) where ....
But still it's neat to have the ability to make any type into a number where you can sensibly provide the Num
operations. The implicit downside is what you've said: the error messages will then be less helpful.