Reputation: 2288
For example, I'd expect the following to simply use a default type, however:
succ mempty
*** Exception: Prelude.Enum.().succ: bad argument
succ minBound
*** Exception: Prelude.Enum.().succ: bad argument
:t succ mempty
succ mempty :: (Monoid a, Enum a) => a
:t succ minBound
succ minBound :: (Bounded a, Enum a) => a
I'd expect (Bounded a, Enum a) => a
to at least match Int
.
Upvotes: 0
Views: 111
Reputation: 120711
It does “simply use a default type”, that's precisely the reason you get this behaviour!
Namely, defaulting tends to always use the simplest type that matches the constraint.
Double
†.Integer
is chosen which is simple in the sense of also having no bounds.Num
type. Often, ()
is sufficient for fulfilling the constraints. Of course it may not be sufficient for fulfilling the semantics you intend, but that basically just means the inferred signature is more general then the one you would have wanted.What I find slightly surprising is that GHCi won't default (Num a, Bounded a, Enum a) => a
to Int
, but gives up finding a default for a
.
I generally avoid relying on defaulting at all. A good rule of thumb is to not make a function's arguments more polymorphic than the result‡ (so the argument types can always be inferred from the context), and then if needed just add a single type annotation to the end of the expression at the REPL. This also works here, of course:
Prelude> succ minBound :: Int
-9223372036854775807
†Arguably, it would be more in line with the philosophy if the default Fractional
type were Rational
.
‡Unless, of course, you have a particular reason. Conversion functions may need to be independently polymorphic in the argument and result, but they are usually only needed when there's too much type information around already which causes contradiction.
Upvotes: 4