Skip to content
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

Derive a type class only for wrapper types (unary product types) #278

Closed

Conversation

oyvindberg
Copy link

Hi again @propensive

We need to reintroduce this functionality into doobie after tpolecat/doobie#1343 .

So this is a first stab at it. If we continue down this path we'll pretty quickly see an explosion of needed types, so it's not necessarily a good path. The whole thing is really a whole lot of ceremony around some critically placed ifs.

I thought about introducing some kind of type-level set of requirements (for instance "must be case class", "must have more/less than N members", and so on) which the macro could query. This, however, was the easiest way forward.

Looking forward to hearing what you think

@propensive
Copy link
Collaborator

I can see that this works, but it does seem like it might be the first step down the wrong path, just due to the amount of additional and repetitive code required.

But I had an idea about this, and I think it might be possible to introduce a couple of macros, completely orthogonal to Magnolia, which materialize typeclass evidence for a particular type if that type conforms to a certain criterion. For example, the macro would materialize evidence of the type Unary[T] if and only if T is a case class with one parameter. These macros would be quite simple.

Then, in the Doobie code, it should then be possible to provide different implicit derivations for the case where the Unary evidence exists, and (at a lower priority) where it doesn't. You would need two different derivation objects (one for each case) and the context bound would need to be added to the combine method, because that doesn't have to conform to an OOP-style interface, whereas the def gen = macro Magnolia.gen definition has a strict shape.

@propensive
Copy link
Collaborator

propensive commented Jan 22, 2021

Here's a rough version of the macro:

case class Unary[T <: Product]()
def unary[T: ctx.WeakTypeTag](ctx.blackbox.Context): ctx.Tree = {
  import ctx.universe._
  val params = weakTypeOf[T].decls.count {
    case p: TermSymbol => p.isCaseAccessor && !p.isMethod
    case _             => false
  }
  if(params == 1) q"Unary[T]()" else ???
}
implicit def gen: Unary[T] = macro unary[T]

@oyvindberg
Copy link
Author

ok yeah, that's a reasonably path going forward for me, thank you for the idea! :) The only negative I see with that is it might lead to quite a few more macro expansions, but we could live with that.

So a follow-up idea to this PR, what do you think about tagging def combine with annotations provided by magnolia for setting constraints such as this?

@magnolia.constraints.minMembers(1)
@magnolia.constraints.maxMembers(1)
def combine(...)

In my head that looks like a flexible way of expressing this and similar constraints which shouldn't complicate the implementation much. Would you be interested in something like that instead?

…ill only be derived for a type if that type has a given range of number of members (so far, though it could be extended)
@oyvindberg
Copy link
Author

oyvindberg commented Jan 23, 2021

I implemented my last proposal as it was pretty easy to do, and pushed it to this same branch. Let's see what you think about it @propensive . I'll update the PR description if you think this is more viable.

On the plus side it leads to less code explosion now, and it'll be extensible if we discover further needs in the future. It also plays well I think with magnolias good error messages.

I think this is a real need actually, lately I have touched 4 type classes with specific needs in this direction

  • Get / Put in doobie which is only defined for types with exactly one member
  • Text in doobie along with a struct representation in a private project which are only defined for types with more than one member

Also just to have said that I think my immediate problem in doobie is solved by your macro suggestion, but I'd prefer this solution, if nothing else to expand fewer macros. If you judge it's not general enough to live in magnolia that's also fine, I'll leave it at this attempt :)

@joroKr21
Copy link
Contributor

#279 (comment)

@lukaszlenart lukaszlenart deleted the branch softwaremill:legacy June 9, 2021 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants