I have a function f op = (op 1 2, op 1.0 2.0)
, which is required to work like this:
f (+)
(3, 3.0)
But without declaring the type of f
it works like this:
f (+)
(3.0, 3.0)
And I'm struggling with declaring the type of f
. It should take an operator which works with all instances of Num
. Is it even possible in Haskell?
The problem is that you forced the operator to work on Fractional
types by applying it to fractional numbers such as 1.0
and 2.0
. Your code typechecks because Fractional
is a subtype of Num
(meaning that each instance of Fractional
is also an instance of Num
).
Following experiment in GHCi should make it clear:
Prelude> :t 0
0 :: Num p => p
Prelude> :t 0.0
0.0 :: Fractional p => p
Prelude> :t 0 + 0.0 -- Fractional taking advantage!
0 + 0.0 :: Fractional a => a
So, if you want to make it work on Num
s entirely, you just need to get rid of those ".0"s:
Prelude> f op = (op 1 2, op 1 2)
Prelude> f (+)
(3, 3)
If you really need the behavior where the second element of returned tuple is Fractional
and the first is some more general Num
, the things get a bit more complicated.
The operator (or actually, function) you pass to f
has to appear as with two different types at the same time. This is normally not possible in plain Haskell, as each type variable gets a fixed assignment with each application – that means that (+)
needs to decide if it is Float
or Int
or what.
This can be, however, changed. The thing you will need to do is to turn on the Rank2Types
extension by writing :set -XRank2Types
in GHCi or adding {-# LANGUAGE Rank2Types #-}
at the very top of the .hs
file. This will allow you to write f
in a manner in which the type of its argument is more dynamic:
f :: (Num t1, Fractional t2)
=> (forall a. Num a => a -> a -> a) -> (t1, t2)
f op = (op 1 2, op 1.0 2.0)
Now the typechecker won't assign any fixed type to op
, but instead leave it polymorphic for further specializations. Therefore it may be applied to both 1 :: Int
and 1.0 :: Float
in the same context.
Indeed,
Prelude> f (+)
(3,3.0)
Protip from comments: the type constraint may be relaxed to make the function more general:
f :: (Num t1, Num t2)
=> (forall a. Num a => a -> a -> a) -> (t1, t2)
f op = (op 1 2, op 1 2)
It will work fine in all cases the previous version of f
would do plus some more where the snd
of the returned pair could be for instance an Int
:
Prelude> f (+)
(3,3)
Prelude> f (+) :: (Int, Float)
(3,3.0)