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.
an instance with generic arity
Edit: As Christian Conkle points out, there are problems with this approach. If you plan to use these instances for anything important or just want to understand the issues, you should read the resources he provided and decide for yourself whether this fits your needs. My intention was to provide an easy way to play with numeric functions using natural notation with as simple an implementation as possible.
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 useApplicative
, like this: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 withApplicative
, 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 equals2
. 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 typeInteger
." This means writing2
or12345
in your source code or in GHCi is equivalent to writingfromInteger 2
orfromInteger 12345
. Either expression therefore has typeNum 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 yourNum
instance for functions, the type offromInteger 2
could very well bea -> Integer
ora -> b -> Integer
. In fact, GHC will happily replace the literal2
with a function, not a number--and a particularly dangerous function too, one that discards any data given to it. (fromInteger n = \_ -> n
orconst n
; i.e. throw out all arguments and just given
.)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 thefromInteger
problem a bit, or at least have2 (3 + 5)
equal16
rather than2
. The answer is simply to definefromInteger 3
as(*) 3
rather thanconst 3
:Note that although this is perhaps morally equivalent to
Num a => Num (a -> a)
, it has to be defined with the equality constraint (which requiresGADTs
orTypeFamilies
). Otherwise we'll get an ambiguity error for something like(2 3) :: Int
. I'm not up to explaining why, sorry. Basically, the equality constrainta ~ b => a -> b
allows the inferred or stated type ofb
to propagate toa
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.