Hi there folks, I'm looking into a ticket for Arro...
# arrow
a
Hi there folks, I'm looking into a ticket for Arrow to try to reduce the duplicated extension functions projected over types (e.g. ListK has around ~5 map functions). One question I'm wondering, does it make sense to provide instances of different typeclasses when they're related? Example: ListK provides an instance for Monad (which has map defined) and another for Applicative (which also has map), but Monad is already an Applicative, so shouldn't be sufficient by providing the former?
☝️ 1
j
We actually have monad-applicative consistency laws which check exactly that. As long as there is the option of getting back the other map by explicitly doing
applicative().run {...}
removing the ext function is perfectly fine with me 👍
a
my question was more oriented as if there would be a case, for example, where Monad would change the behaviour of the extended Applicative, so we really need the explicit Applicative instance instead of just passing a Monad one
i'm not sure if we can selectively remove the generated extensions functions tho 😕
j
Yes there is
I break this law on purpose in propcheck generators because
ap
provides better interleaving and thus better shrinking
Also parallel applicatives etc all break this
This can be solved with a new type as a wrapper, but that is not easy to use in kotlin (no free newtypes and no easy derivation of other instances...)
This can be worked around as well: I currently explictily override all default implementations to avoid ever calling into
flatMap
. That is quite a bit of extra work, but when doing that it is again safe to remove all other generated overrides.
a
I'm wondering if relying on inheritance to then change behaviour would not bring more problems than solutions tho
because then, it might depend on the import I choose for my xx function if it does something in a way or another, no?
j
You are right! And giving this another thought I think the correct way to resolve wanting a method from a different constraint is overriding it everywhere it gets a default implementation. This is never the case in arrow (only defines parApplicative as a seperate instance not with
@extension
) and I think every code that breaks monad-applicative-consistency just has to deal with this as it's by no means recommended to do so. So yeah the generated functions can be highest constraint that defines it wins for sure!
If you however want to remove all lower instances, e.g. if it has Monad, don't even define Applicative etc then I am also for that as long as the extension functions:
applicative()/functor()
etc still get generated (they can point to monad) because without automatic type class resolution this will get confusing for those who don't know the entire hierarchy
^^ Would also solve the problem that some datatypes don't have instances of all typeclasses that they could have (ie selective, invariant missing). By pointing all of those to monad we'd have a much easier way of using them
a
uhm good point, i'll investigate! Thanks @Jannis!
j
Thanks for taking this on! This is quite a mess right now 👍
r
lower instances can’t be removed
The current generator has a limitation that it only projects those methods from the class you extend AFAIK
👍 1
so if you don’t provide an instance of Applicative you don’t get the top level functions from Applicative.
In Meta that is not the case and you can just implement the most powerful typeclass to get all of it without top level functions
a
fair enough, thanks for the info!! 🙌