-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PROPOSAL - Migrate Scalameta Macros #6
Comments
Totally agree. |
I recommend that you look very hard at non-macro solutions where at all possible, or alternatively depend on existing libraries rather than introducing new macros of any form ... all of this will change in Scala 3 and adding new boat anchors is not a good idea at this point. |
@milessabin : in the context of Any guidance about fit with I understand " look very hard at non-macro solutions where at all possible" but for code generation macros is a good solution and Please advise further. |
@SemanticBeeng it's about macros in general. The only thing that's certain at this point is that scala.reflect macros are not viable in the long term. It's still unclear what will replace them. |
Understood. My best knowledge for long term is Martin's vision here which combines Sorry if this extrapolation is "unsafe" but given that Very interested to learn better as things evolve. Will keep an eye. UPDATE: About
UPDATE: Related to |
I highly recommend migrating to scala-reflect macro annotations since Scalameta macro annotations are not a viable solution even in the mid term (or short term for that matter). Excluding dependency upgrades and a few rare bug fixes, Scalameta macro annotations have not seen any development since late 2016. There will be no Scalameta paradise release for 2.13. The Slinky project migrated to scala-reflect macro annotations and reported "drastically improved error messages" https://medium.com/@shadaj/introducing-slinky-0-5-0-21deb2546d65#0692 IntelliJ has improved documentation for writing custom "injectors" that enables IDE support for scala-reflect macro annotations https://github.com/JetBrains/intellij-scala/wiki/Library-Injector |
The link @olafurpg posted is obsolete now, here's the updated one: https://github.com/JetBrains/intellij-scala/wiki/Library-Extensions |
I somewhat attempted to do this but ran into a macro issue that I can't figure out. I implemented finalAlg and autoFunctorK. https://github.com/dispalt/tagless-redux The macro issue is stacking multiple macros with no companion object present somehow nets a missing companion class, which I am sure can be solved I am just running out of steam on how to do it. |
@dispalt, thank you for the report. It's helpful to learn that. |
And I am not saying it can't be done, and I am sure someone with more experience with macros could figure it out, but it's a decent start |
It looks like we need some scalamacro expertise to tackle this, which I lack (hence the choice of scalameta at the first place which I regret). The easiest way is probably me partner with a macro guru together on this - I am going to try recruit one. |
I was (emphasis on past tense) planning on writing something equivalent to I created a shell project, now decomissioned, in https://gitlab.com/fommil/attic/tree/master/scalaz-free which should save some time on the sbt setup side. The scope of this project was much larger than just Also, if anybody wants to write an |
@fommil That's a noble cause, but what is to guarantee compiler plugin APIs will remain compatible from Scala 2 to Scala 3 😄 I would propose the following migration path (which can be taken in many small steps by different contributors):
Here is a starter blackbox macro for |
@joroKr21 I don't think the code would survive, but surely the principles would. I definitely agree to the use of blackbox as much as possible. This is also what is encouraged in the annotation plugin. An immediate benefit of an annotation plugin is that a) metals / scalaide will support it and b) there is a clear path from there to write an intellij plugin (examples provided). These points become invalidated if paradise is maintained to support the PC. |
@joroKr21, there are no new types generated. I think what you have with blackbox is where we want to be. cats-tagless is a bit more complex due to the need to support all kinds of different shape of algebra type signatures, e.g. I think blackbox macro probably would suffice. I can see two strategies:
Which one would you prefer? |
That's ok, but how does the annotation decide which type parameter ( Regarding your suggestions - I'm not sure before I look deeper in the code, but I think I prefer starting from scratch and making one typeclass work. |
the annotation doesn't decide which F[], it's based on some convention rules, namely |
Dependent types are the most tricky because they require symbols to define which we don't have yet before typechecking the definition :/ |
@joroKr21 maybe for phase one we first drop support for dependent type? |
Let me try a few ideas first |
You can check out a couple semi-auto style versions I did here. https://github.com/dispalt/tagless-redux/blob/master/tests/src/test/scala/cats/tagless/tests/DerivationTests.scala |
Managed to make it work for dependent types (but not yet refined types). WIP here: #8 It would be nice to document the necessary and sufficient conditions to generate instances. For example, for https://typelevel.org/cats-tagless/typeclasses.html gives a rough overview, but it doesn't mention the "lower-kinded" versions ( |
@joroKr21 yeah, I meant to add that documentation but never get to. |
For now the rule of thumb to determine if an algebra can have autoFunctorK is to whether you can easily manually write the |
for something to be able to have a FYI the equivalent Haskell boilerplate looks something like instance FFunctor UserApi where
ffmap nt (UserApi f1 f2 f3) = UserApi (nt f1) (nt ... f2) (nt ... f3) where Otherwise, you can fall back to manual arity with something like instance FFunctor UserApi where
ffmap nt (UserApi f1 f2 f3) = UserApi (nt f1) (nt . f2) (nt .: f3) using http://hackage.haskell.org/package/composition-1.0.2.1/docs/Data-Composition.html Of course you still need to special-case fields that don't have any parameters. You should be able to do that with only a syntactic analysis, although I think you might be inside the blackbox macro at that point. (see local capabilities in mtl for context) I should imagine that creating the equivalent Scala boilerplate would be sufficient, and then just let the compiler fail normally if an instance isn't possible. I was never able to work out how to intercept typer errors to provide a custom message, using either plugins or macros... but it'd be good to be able to provide some information if it fails to typecheck. |
Hey guys I pushed an intellij integration that's very simple. You can check out the example here, https://github.com/dispalt/tagless-redux-ijext It's rudimentary but it's pretty neat. Basically it will supply the fake methods depending on the presence of an annotation. It uses the extension method which works with a recent version of intellij. So if you clone that repo, import to intellij, it will say it detected some extensions would you like to load them and if you hit yes, it should work. |
@fommil That's very neat. I've seen the "dots" before but I had completely forgotten about them. However I don't know of any port to Scala (maybe opportunity for a pet project?). @dispalt Thanks, the plugin should work regardless of how the instances are generated right? Also it looks like you already have a working implementation based on |
I did port finalAlg autoFunctorK to paradise, then ported them to a semi auto style. Did not yet port them to paradise + blackbox. But the code is crap and I really only care about FunctorK personally. Yes it does work based on fq annotation names so it should drop in work with a new annotation easily. Also the intellij code isnt great just wanted to play around, it's not a bad framework, just too much stuff to learn so I am coding by braile. |
btw if you use The more I think about it the more I'm convinced it should work... there might be a couple of branches to add to the plugin code to handle the HKT but the semiauto boilerplate is basically identical to the normal-kinded version. |
It turned out that switching to old |
Yes, I think it will still take some time to migrate, because there are also a lot of methods generated on the companion objects which would have to be rewritten. And it would have to be a big rewrite, it can't be done piecemeal. Maybe we can cut some less used features but I'm not sure yet 🤔 About releasing the macros for "semi-auto" lovers, do we need a separate module? Would the scala meta dependency in itself be a problem? Or maybe just not enabling the compiler plugin is good enough. Ideally scala meta should be a compile-time only dependency (i.e. provided), but I don't know if it works in practice. I guess I'm also not sure whether you mean a temporary module, until the migration is done, or have it in general? We also shouldn't forget to document the "semi-auto" derivation 😺 TLDR: I think it makes sense to release the blackbox macros before we're done with the rest of the migration, because some users would anyway use only that. But I'm not sure why we would need a separate module. |
I guess one reason to release a separate module (and keep it) would be to make it lighter-weight for people who want to use "semi-auto" only or |
@joroKr21 making the Scalameta dependency provided and have user adding it together with the complier plug in is a good idea. Yet another benefit for having a separate module is the ability to release it on Scala 2.13. Since we are going to migrate the rest of the functionality gradually, it might be easier to have the new code on a new module and not touch the legacy module at all. We can rename the old one legacy-macro. We have two options on how to make this reorganization regarding inter module dependency
I am fine either way. I can do 2 phases , do option 1 first and then 2. Wdyt? |
Okay I'm sold on releasing a separate module 👍 Option 1. is less work so I would go for that. We can cut this dependency later on as well. |
On the other hand, I'm not sure what the migration path would look like anymore. I was planning to just replace the existing code with |
@joroKr21 you should still be able to do that if, for any code you want to replace, you write the new code in the new module and change the usages in the legacy module to use this new code instead right? Unless you need dependency the other way as well, i.e. new code depending on legacy code? |
That wouldn't work anyway. I guess it doesn't really matter if we replace old code or just write new code based on the old one 😄 |
I might have missed this but how are you all thinking about autoDerive and fullyRefined? I think that will change the amount of work for the annotations. I think the simplest is just ditch the meta annotations and do the reflect version, its not like there are really any changes that need to happen to the meta version. |
@dispalt we were talking about releasing a separate module without macro annotations. |
yeah definitely, I just wasn't sure if the plan was to then do macro annotations with or without auto derive. I was thinking of maybe trying to do the macro annotations using the semi auto macros, and was wondering what the thought is on autoDerive, etc. |
@dispalt I think we can create reflect.macro annotations little by little. If we can add macro annotations that simply calls the semiauto macros that would be great. The next step could be the instance summoning |
Yeah I agree, are you going to re-arrange the code to a new module, or keep as is, Ill probably wait til that's done before doing anything. |
the new module is ready. we can start porting annotations there one by one. |
@fommil, I tried scalaz-deriving, but unfortunately it doesn't work out of the box. It looks like it's missing some kind checks and it thinks it should require |
@joroKr21 cool, that is pretty much what I expected. It should be relatively easy to fix. BTW you're supposed to put the The easiest way to get this working would be to add a higher kinded example to https://github.com/scalaz/scalaz-deriving/tree/master/deriving-plugin/src/test/scala If that all checks out ok, then the bug might be in the blackbox macro ... https://github.com/scalaz/scalaz-deriving/blob/master/deriving-macro/src/main/scala/scalaz/macros/DerivingMacros.scala you might need to add some flag to indicate kindedness or something, then you can switch on it in that file. Good luck! |
The macro annotations in
cats-tagless
are implemented using the macro system of scalameta.However, the development of macros and macro annotations in Scala Meta was closed, as explained
here.
Current support for Scalameta macros may not work in next versions of the scala compiler.
On the other hand, the Scala compiler has recently incorporated the macro-paradise compiler plugin, which enables support for macro annotations based on
scala-reflect
, into their main codebase.The goal of this issue is to migrate existing macros to the
scala.reflect
based macro annotations.The text was updated successfully, but these errors were encountered: