Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making numeric functions an instance of Num?

I want to be able to compose numeric functions in haskell using binary operators. So, for example, with unary numeric functions:

f*g

should translate to:

\x -> (f x)*(g x)

and similarly for addition. Making your own operator to do this is pretty straightforward, but I'd really like to just make Num a => a -> a functions an instance of Num, but I'm not sure how to do so.

I'd also like to make this arity generic, but that might be too much trouble for how difficult it is to do arity generic functions in Haskell, so it might just be better to define seperate Num a => a -> a -> a, Num a => a -> a -> a -> a, etc... instances up to some reasonably large number.

like image 347
Nathan BeDell Avatar asked Oct 22 '14 19:10

Nathan BeDell


1 Answers

The idiomatic approach

There is an instance of Applicative for (->) a, which means that all functions are applicative functors. The modern idiom for composing any functions in the way you're describing is to use Applicative, like this:

(*) <$> f <*> g
liftA2 (*) f g -- these two are equivalent

This makes the operation clear. Both of these approaches are slightly more verbose but seem to me to express the combination pattern far more clearly.

Furthermore, this is a far more general approach. If you understand this idiom, you will be able to apply it in many other contexts than just Num. If you're not familiar with Applicative, one place to start is the Typeclassopedia. If you're theoretically inclined, you may check McBride and Patterson's famous article. (For the record, I'm using "idiom" here in the ordinary sense, but mindful of the pun.)

Num b => Num (a -> b)

The instance you want (and others besides) is available in the NumInstances package. You can copy out the instance @genisage posted; they're functionally identical. (@genisage wrote it out more explicitly; comparing the two implementations may be enlightening.) Importing the library on Hackage has the benefit of highlighting for other developers that you're using an orphan instance.

There's a problem with Num b => Num (a -> b), however. In short, 2 is now not only a number but also a function with an infinite number of arguments, all of which it ignores. 2 (3 + 4) now equals 2. Any use of an integer literal as a function will almost certainly give an unexpected and incorrect result, and there's no way to warn the programmer short of a runtime exception.

As spelled out in the 2010 Haskell Report section 6.4.1, "An integer literal represents the application of the function fromInteger to the appropriate value of type Integer." This means writing 2 or 12345 in your source code or in GHCi is equivalent to writing fromInteger 2 or fromInteger 12345. Either expression therefore has type Num a => a.

As a result, fromInteger is absolutely pervasive in Haskell. Normally this works out wonderfully; when you write a number in your source code, you get a number--of the appropriate type. But with your Num instance for functions, the type of fromInteger 2 could very well be a -> Integer or a -> b -> Integer. In fact, GHC will happily replace the literal 2 with a function, not a number--and a particularly dangerous function too, one that discards any data given to it. (fromInteger n = \_ -> n or const n; i.e. throw out all arguments and just give n.)

Often you can get away with not implementing inapplicable class members, or by implementing them with undefined, either of which gives a runtime error. This is, for the same reasons, not a fix to the present problem.

A more sensible instance: pseudo-Num a => Num (a -> a)

If you're willing to restrict yourself to multiplying and adding unary functions of type Num a => a -> a, we can ameliorate the fromInteger problem a bit, or at least have 2 (3 + 5) equal 16 rather than 2. The answer is simply to define fromInteger 3 as (*) 3 rather than const 3:

instance (a ~ b, Num a) => Num (a -> b) where
  fromInteger = (*) . fromInteger
  negate      = fmap negate
  (+)         = liftA2 (+)
  (*)         = liftA2 (*)
  abs         = fmap abs
  signum      = fmap signum

 

ghci> 2 (3 + 4)
14
ghci> let x = 2 ((2 *) + (3 *))
ghci> :t x
x :: Num a => a -> a
ghci> x 1
10
ghci> x 2
40

Note that although this is perhaps morally equivalent to Num a => Num (a -> a), it has to be defined with the equality constraint (which requires GADTs or TypeFamilies). Otherwise we'll get an ambiguity error for something like (2 3) :: Int. I'm not up to explaining why, sorry. Basically, the equality constraint a ~ b => a -> b allows the inferred or stated type of b to propagate to a during inference.

For a nice explanation of why and how this instance works, see the answer to Numbers as multiplicative functions (weird but entertaining).

Orphan instance warning

Do not expose any module that contains--or imports a module that contains--or imports a module that imports a module that contains...--either of these instances without understanding the problem of orphan instances and/or warning your users accordingly.

like image 170
Christian Conkle Avatar answered Sep 20 '22 18:09

Christian Conkle