-
Notifications
You must be signed in to change notification settings - Fork 12
Is there an easy way to make functor instances? #31
Comments
Hi, This is a totally fine place to ask a question like this. And it's a tricky one! In the current setup, there's no easy way to do what you're asking, but I just played around with it for a bit and made a new -- | A sample record like the one you mentioned
r = #a .== Just (2 :: Int) .+ #b .== [True]
-- | A function that 'show's the value in the functor
foo :: (Functor f, Show a) => f a -> f (Const String a)
foo x = fmap (Const . show) x and then: > Rec.mapF @Functor @Show @(Const String) @("a" .== Maybe .+ "b" .== []) @("a" .== Int .+ "b" .== Bool) foo r
#a .== Just (Const "2") .+ #b .== [Const "True"] If this is useful to you, I'll try to clean it up and commit the code. |
This is very interesting! I'll have to play around with it and see what I can do with it. I did manage to get a working prototype of what I am after, which are reusable "Data types a la carte"-style constructors using row-types (here: https://gist.github.com/woehr/a24a363465aef345deb695fdf3cef000). I managed this by shamelessly using the same approach as http://hsyl20.fr/home/posts/2018-05-22-extensible-adt.html. Indeed, I was originally using this library, but the compile times started to be come unbearable with larger variants. (I don't think what I've implemented will hurt compile times, but I'm still new to type level programming like this, so I'm not 100% sure). I am unsure, however, whether this approach is a good fit for row-types because I'm "manually" poking around the Row internals. Perhaps BiForall could be used more effectively instead. Edit: I apologise for the rambling and open-ended question. I'm going to experiment with Biforall now. Suggestions welcome. :) |
Interesting! I have some questions and thoughts. So, are you saying you were initially using just the standard row-types library, but the compile times got really bad? How large were your variants? I've found that one thing that can make a big difference is how two row types are joined together (that is, associativity has no bearing on behavior, but it definitely affects performance), so you might be able to get better performance by tweaking the syntax of your definitions. I'm really intrigued and happy that you're using variants. Most people are only interested in records, so I haven't done much work on variants. For instance, Unfortunately, "manual poking around the internals" is pretty necessary right now to do interesting behaviors with row-types. If you look at the definitions of The design of The reason newtype VarWrapF (r :: Row (* -> *)) x = VarWrapF {unVarF :: Var (ApplyRow x r)} Clearly, the newtype VarWrapF x (r :: Row (* -> *)) = VarWrapF {unVarF :: Var (ApplyRow x r)} then this would be an easy candidate for |
I meant the compile times of the library discussed in the linked blog. I haven't had any compile length issues with row-types yet.
This would be very convenient since every operation implemented as a type class requires a similar instance (for example, OverList in the gist). |
Of course, if we can use -- This is the type you've already defined
newtype VarWrapF (r :: Row (* -> *)) x = VarWrapF {unVarF :: Var (ApplyRow x r)}
deriving instance Forall (ApplyRow x r) Eq => Eq (VarWrapF r x)
deriving instance Forall (ApplyRow x r) Show => Show (VarWrapF r x)
-- Some newtype helpers
newtype VarWrapF' x (r :: Row (* -> *)) = VarWrapF' {unVarF' :: Var (ApplyRow x r)}
newtype FlipApp (a :: *) (f :: * -> *) = FlipApp (f a)
instance Forall r Functor => Functor (VarWrapF r) where
fmap :: forall r a b. Forall r Functor => (a -> b) -> VarWrapF r a -> VarWrapF r b
fmap f = VarWrapF . unVarF' . go . VarWrapF' . unVarF
where
go = metamorph' @_ @r @Functor @(VarWrapF' a) @(VarWrapF' b) @(FlipApp a) Proxy doNil doUncons doCons
doNil = impossible . unVarF'
doUncons l = (FlipApp +++ VarWrapF') . flip trial l . unVarF'
doCons :: forall ℓ f ρ. (KnownSymbol ℓ, Functor f)
=> Label ℓ -> Either (FlipApp a f) (VarWrapF' b ('R ρ)) -> VarWrapF' b ('R (ℓ :-> f ': ρ))
doCons l (Left (FlipApp x)) = VarWrapF' $ unsafeMakeVar l $ f <$> x
doCons _ (Right (VarWrapF' v)) = VarWrapF' $ unsafeInjectFront v I'm not sure why I didn't think of that earlier. |
Thanks for showing me how |
Me again... I hope it's okay for me to solicit feedback from this thread. I've gone ahead and wrapped up what I was asking about in this thread into its own package, and would love to get some feedback before I release it to the wider world (especially if I'm using/abusing row-types appropriately). The repo is at https://github.com/woehr/open-adt and I've written documentation and a tutorial module which I've uploaded at https://woehr.github.io/open-adt/. Thanks! |
Cool! I'll definitely take a look, but I may not get to it for a week or two. |
Thank you, and whenever you get to it is fine, of course. I greatly appreciate the feedback. |
I like it! It took me a bit to really get what you're trying to do (I don't have that much experience with recursion schemes, but reading this forced me to learn quickly), but it's pretty interesting, and it's a neat use case for row types. I also looked specifically at the implementations of some of the functions (like Cool stuff! |
Thank you so much for the feedback! It's very reassuring to get the positive feedback. |
I hope this is an okay place to ask a question.
Basically, if I have a row type, say,
"a" .== Foo x .+ "b" .== Bar x
, and Foo and Bar are both Functors (or any class, really), is there an easy way to map over the entire structure and get a result of"a" .== Foo y .+ "b" .== Bar y
?I've been trying to use
map
andtransform
, but have had no success using them so far (my type-level experience is limited).Thanks in advance!
The text was updated successfully, but these errors were encountered: