I have a Haskell project that uses fmap
on data structures very intensively. In order to avoid traversing the same data structure again and again, while retaining the possibility to use fmap
liberally, I decided to use the Coyoneda
-type to guard some of the bigger structures.
The Coyoneda type has constructor Coyoneda :: (x -> y) -> f x -> Coyoneda f y
. The idea is that by parametricity, more precisely by the co-Yoneda lemma, the types f a
and Coyoneda f a
are isomorphic, but the advantage of the Coyoneda
type is that it postpones the actual structure traversal.
For example, in the following code, the first expression traverses the underlying structure 3 times, and the second one only once:
fmap k $ fmap h $ fmap g $ (x :: f a)
lowerCoyoneda $ fmap k $ fmap h $ fmap g $ liftCoyoneda $ (x :: f a)
Indeed, the second line reduces as follows:
lowerCoyoneda $ fmap k $ fmap h $ fmap g $ liftCoyoneda $ x
lowerCoyoneda $ fmap k $ fmap h $ fmap g $ Coyoneda id x
lowerCoyoneda $ fmap k $ fmap h $ Coyoneda g x
lowerCoyoneda $ fmap k $ Coyoneda (h . g) x
lowerCoyoneda $ Coyoneda (k . h . g) x
fmap (k . h . g) x
so it effectively postpones the actual structure traversal until you apply lowerCoyoneda
.
This had a massive impact on time and a big impact on space performance, but I am still not satisfied. For this reason, I want to start using deepseq
(or similar) as (indirectly) provided by the NFData
typeclass. So I want to implement NFData
for my functors, which now have Coyoneda
-guards in their definitions.
The trouble is that reducing lambdas to normal form doesn't shrink the size of the data in their closure.
Mathematically, it would be sound to simply reduce Coyoneda g x
to Coyoneda id (fmap g x)
. I am wondering if there is some unsafe hack - some sort of runtime rewrite rule - to implement this. Other solutions are also appreciated.
rnf
, notdeepseq
.) I'm not certain about whether it would actually pay off for performance anyway. Usingrnf
/deepseq
is usually very bad for performance. It tends to change O(n) algorithms to O(n^2). – SkyjackCoyoneda g x
toCoyoneda id (fmap g x)
" - isn't that justliftCoyoneda . lowerCoyoneda
? – Venutirnf
is not a function with an input and an output, but calling it reduces its argument "wherever it is in memory" so to say, i.e. in such a way that anything sharing that argument will also work on the reduced version in the future. I would like to applyliftCoyoneda . lowerCoyoneda
as part of the reduction performed byrnf
, so not as an ordinary Haskell function. If there is any Haskell feature that allows me to do this, it is going to be an exceptionally unsafe feature. – Emalee