I was using a Map String (Int, Int) where the two Ints were used as the numerator and denominator to form a Rational to be passed to fromList.
Then I realized that in a point in my code I had used those two Ints the other way around (as denominator and numerator, i.e. swapped). It took some time to find out what was wrong, so afterwards I thought that maybe I should use two dedicated types, so I wrote
newtype MyNum = MyNum Int
newtype MyDen = MyDen Int
but then I had to add a few instances for everything else to work (given the uses I make of those Ints, I had to add deriving (Eq, Ord, Show, Read)), and also to add some two functions to unwrap the Ints from within the two types so that I could actually apply things like (+1) to those wrapped Ints.
But this means that code starts looking a bit ugly, with things like (MyNum . (+1) . unwrapMyNum), whereas something like (+1) <$> would be much preferrable.
But that means that MyNum should be a Functor; but it can't because it's a hard type, not a type constructor.
But I don't want to make it a type constructor because I don't want to wrap anything in it other than a Int.
Any suggestion?
I think the actual problem has nothing to do with your concrete question. Just don't use tuples, use a suitable type that expresses what both integers represent together. In this case the obvious choice would be to use
Ratio Int, with the caveat that it does not store arbitrary pairs but properly normalises the fractions (which is generally a good thing). If that's not appropriate for you, just write your ownRatiotype.That said, there are also many things you can do to make a newtype wrapper around a single type more convenient:
Derived instances. It seems like you still used numerical operations on the wrapped type. That's easy to enable via the
DerivingStrategiesextension:and now
MyNumis basically a fully featured clone ofInt, you can directly write expressions such asnegate (n + 9)forn :: MyNum, no need for wrapping and unwrapping. (But the compiler still baulks when you pass aMyNumto something that expects aMyDen.)Mono-functor. If you rather want to emphasize
MyNumbeing a container for anInt, and not a different kind of number type of its own right, then there's theMonoFunctorclass. You can instantiateand then write e.g.
omap (+1) n, which is like(+1)<$>nbut doesn't require the container to support anything butInt. Check out also the other classes of the package.General wrapping / unwrapping helpers. There are alternatives for explicitly operating on newtype- or other contained data that work also when special classes are not suitable, and can be more convenient than a traditional pair of accessor and constructor. Among them are
coerceand lenses (more specificallyIsos).