0

I've written a macro annotation processor that generates all the same methods you would get from declaring a case class, but providing hash-consing. It was a bit tricky, but overall I'm very pleased with the results. However, I'm seeing some behavior around the apply method I have yet to solve.

Previously when a class Foo was a case class or has a manually defined apply method I could write code like foos.map(Foo). However, now that the method is generated by the macro processor it will complain with an error like the following

type mismatch;
[error]  found   : Foo.type
[error]  required: String => ?

Now I can just rewrite the code as foos.map(Foo.apply) or foos.map(Foo(_)) and it will work, but I haven't been able to discern any difference in the code that I am generating that would cause this difference in behavior.

I suspect it is something like the Scala compiler is too eagerly resolving the symbol to the type name rather than the object name or some such, but if there is a way to do better here it would be good to know.

user1063042
  • 273
  • 2
  • 11
  • 1
    Make your generated object `extend String => Foo` _(arguments from apply to the object)_. That is what case classes do. - And, just be sure, override the `apply` method. – Luis Miguel Mejía Suárez May 10 '19 at 21:00
  • Based upon all empirical evidence I've seen so far that is not what case classes do. Just to be sure, I used the `:javap` functionality in the REPL to verify that the only classes a case class extend are `Product` and `Serializable`. – user1063042 May 10 '19 at 21:10
  • Also, adding the `override` modifier to the generated `apply` method does not resolve it either. – user1063042 May 10 '19 at 21:11
  • 2
    @user1063042 Case class extends Product and Serializable but its companion object extends Function. – Dmytro Mitin May 10 '19 at 21:12
  • @DmytroMitin Ah, indeed. If I use `:javap` on `Foo$` it shows that it extends function. However, that only seems to be the case if the class has a single argument list. Do you know how this is handled for case classes with multiple argument lists? – user1063042 May 10 '19 at 21:19
  • @user1063042 case classes are not intended for multiple arguments lists. All of their behaviors are broken for the second argument like the by value comparison and hashcode. – Luis Miguel Mejía Suárez May 10 '19 at 21:26
  • @LuisMiguelMejíaSuárez If you happen to rely on the additional argument lists factoring into default generated hashCode, etc. sure it is broken. But there are still fruitful use cases for having an additional argument list. – user1063042 May 10 '19 at 21:32
  • @user1063042 I really do not see the point of having a case class with a second argument list if everything will not work... But anyways, in such case the generated object will not extend the `Function1` trait, not even for the first argument list. – Luis Miguel Mejía Suárez May 10 '19 at 21:39
  • @LuisMiguelMejíaSuárez (a) the additional arguments might not be semantically meaningful when it comes to object equivalence and (b) if they happen to be, Scala does allow you to still override equals and hashCode on case classes. – user1063042 May 10 '19 at 21:46
  • @LuisMiguelMejíaSuárez There are several use cases for multiple parameter lists: parameters of path-dependent types, implicit parameters, multiple varargs parameters... And case class is still a case class with respect to the first parameter list i.e. better than nothing. – Dmytro Mitin May 10 '19 at 21:46
  • @user1063042 By the way it's possible that you can't completely emulate case class behavior with macro annotation because compiler not only generates some methods but also treats case classes differently https://stackoverflow.com/questions/24227037/unapply-method-of-a-case-class-is-not-used-by-the-scala-compiler-to-do-pattern-m https://stackoverflow.com/questions/39004088/how-to-reproduce-case-class-behaviour-with-apply-unapply-methods – Dmytro Mitin May 10 '19 at 22:06
  • @DmytroMitin Thanks! Unfortunate, but good to know. So far for my purpose it is proving a close enough facsimile. Arguably hash-consing should be orthogonal to generating case class-like methods, but as far as I can tell there is no straightforward way via an annotation processor to override or replace the factory method of a case class. It looks like the Scala compiler itself adds those in a later phase. I might be able to use the pattern described in https://stackoverflow.com/questions/5827510/how-to-override-apply-in-a-case-class-companion – user1063042 May 11 '19 at 12:34
  • Oh wow. After doing some more digging, versions of Scala 2.12.2 do allow overriding the companion apply method. For no particularly good reason other than not wanting to get all the other projects in the company to upgrade their Scala dependency, we've been using 2.12.1. So it may be possible to implement all of this way more simply. – user1063042 May 11 '19 at 12:43

1 Answers1

1

As pointed out by @LuisMiguelMejíaSuárez and @DmytroMitin, my critical oversight is that (most) case class companion objects inherit from a FunctionN class. Adding that to the parents of the new or existing companion object resolve the problem.

user1063042
  • 273
  • 2
  • 11