2

In ghci, the :t command doesn't see anything wrong in expressions like 3 4, (sin . (+)) 1 2 3 4, sin . (+) 1 2 3 4 or (-) - (-) and happily provides type information for them:

3 4 :: (Num (a -> t), Num a) => t

sin . (+) 1 2 3 4
  :: (Floating c, Num (a1 -> a2 -> a -> c), Num a1, Num a2) => a -> c

(sin . (+)) 1 2 3 4
  :: (Floating ((a -> a1 -> t) -> a -> a1 -> t), Num (a -> a1 -> t),
      Num a, Num a1) =>
     t

(-) - (-) :: (Num (a -> a -> a), Num a) => a -> a -> a

I am sure there is a reason behind that. But I can hardly imagine a context in which (3 4) makes sense (I don't mean something practical, rather something that at least compiles) and would appreciate if someone gave me a clue.

zabolekar
  • 1,624
  • 1
  • 12
  • 17
  • 1
    Actually, `3 4` does compile! If it didn't, you would see a type error, not a type. It doesn't actually matter that there is no instance The reason that `3 4` compiles is that type classes are open, so someone might come along and define an instance `Num (a -> a)` at which point `3 4` has a value. – user2407038 Nov 02 '14 at 04:43
  • 1
    I don't know whether this is a duplicate per se, but see [this question and its answers](http://stackoverflow.com/q/26515102/1186208). Thank you for (implicitly) agreeing with me that `(3 4)` is an abomination--but yes, if you write the instance, it complies and runs without complaint. (It's equivalent to 3.) – Christian Conkle Nov 02 '14 at 04:46

3 Answers3

4

A lot of things "typecheck" with crazy or impossible inferred constraints. One of the reasons we have map :: (a -> b) -> [a] -> [b] rather than only the strictly more general fmap :: Functor f => (a -> b) -> f a -> f b is that if you use the latter incorrectly, you're likely to end up with a an error No instance (Functor [something obviously not a functor]) rather than a friendlier Expecting [a], got ....

The situation with Num/Floating is particularly unnerving because all numeric literals are overloaded using typeclasses. The compiler will happily infer virtually any type for any integer literal, and if possible just stick it in as a constraint. Normally this will end up giving an instance resolution error.

Furthermore, it is possible to write a not-completely-nonsensical instance Num b => Num (a -> b). That's a different question, though. As I noted there, it strikes me as a questionable idea, and it's deliberately not in base. (Or, for that matter, the alternative numeric typeclass packages I looked at.)

Community
  • 1
  • 1
Christian Conkle
  • 5,932
  • 1
  • 28
  • 52
  • 1
    I don't think it is wrong to be able to define `Num (a -> b)` per se, but this is one of a number of cases where GHC could have been a lot more helpful explaining that if type inference ends up requiring a non-existent instance for functions, the real problem is *probably* applying functions to the wrong arguments. – Ørjan Johansen Nov 02 '14 at 05:38
  • 1
    I have seen a Num instance for functions used before as a demonstration to get natural looking units on numbers, like `3 kilo meters`, where it's set up to automatically convert to the base unit. It was clever, but I'd never use it in real code – bheklilr Nov 02 '14 at 16:26
  • Thanks for the reason and the link. – zabolekar Nov 02 '14 at 18:52
1

You can make almost anything a Num. If FlexibleInstances is turned on, you might have a mostly-undefined-but-defined-enough-to-demonstrate instance:

{-# LANGUAGE FlexibleInstances #-}
instance Num (Integer -> Integer) where
    fromInteger = (+)
    (+) = (.)
    (-) = undefined
    (*) = undefined
    negate = undefined
    abs = undefined
    signum = undefined

Then with a few type annotations you can get a sensible result:

ghci> 2 (3 :: Integer) :: Integer
5
icktoofay
  • 126,289
  • 21
  • 250
  • 231
1

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.

Luis Casillas
  • 29,802
  • 7
  • 49
  • 102