Factory methods of case classes

The following does work:

case class Foo(thunk: () => String)

object Foo:
   @targetName("apply_byName")
   def apply(str: => String): Foo = new Foo(() => str)

@main def test() =
   val f = Foo("bar")

However, I wonder if I’m missing something cleaner (i.e., without the annotation). I want class Foo to be a case class for pattern-matching; I also want users to not see the thunk and use a by-name argument instead. Without the annotation, there’s a conflict between my apply method and the one that’s automatically available for case classes.

  • If you only want the case class for pattern matching, you could use a plain class and implement a custom #unapply() on its companion object.
  • Alternatively, you could add an implicit DummyImplicit parameter to your case class’ parameters.
1 Like

Try making the case class abstract

abstract case class Foo(s: () => String)

object Foo:
  def apply(s: => String) = new Foo(() => s) {}

val x = Foo("woohoo")
// x: Foo = Foo(repl.MdocSession$$$Lambda$9836/833363921@408a20be)
x.s()
// res0: String = woohoo
val Foo(s) = x
// s: Function0[String] = repl.MdocSession$$$Lambda$9836/833363921@408a20be
s()
// res1: String = woohoo

Note how the pattern matching still works.

2 Likes

I wanted to suggest the same thing as @sangamon.

And I don’t really get why you would want to do it this way.

Basically () => String is just another why of specifying a by-value argument. So, you have double indirection there (calling thunk will evaluate (ie. call) the by-value argument each time)

Also, pattern matching on the thunk will probably not work anyway, does it? (I mean you cannot compare the thunk function in any meaningful way, apart from checking object identity) So, pattern matching is just useful for extracting the thunk from the case class.

Maybe you oversimplified your case too much and there is a deeper reason to this…?

Nice, wasn’t aware that this if even possible (as you cannot inherit from a case class). Here’s a more detailed writeup of this pattern from @tpolecat.

I want to pass the argument by name because it’s cleaner on the calling site, but I also need to store it unevaluated. I don’t have access to the hidden thunk, do I? (I could use a by-name constructor argument, but only if I don’t use a case class.) Also, pattern-matching comes into play because there are other classes involved, so it’s only to distinguish Foo from other types, not between different instances of Foo.

Thanks for all the suggestions, both “clean” and “trick-based”.

If you don’t require extraction, then case f: Foo => ??? should work out of the box for plain classes, too.

1 Like

Exactly. And, you could also do this:

case class Foo()(s: => String)

such that the by-value parameter does not participate in pattern matching. I am just wondering what happens if you pass a by-value parameter by value again, if you wanted to provide a convenience factory method:

object Foo:
  def apply(s: => String) = Foo()(s)

Oh no, this does not work:

Double definition:
def apply()(v: => String): Foo in object Foo at line 5 and
def apply(v: => String): Foo in object Foo at line 8
have the same type after erasure.

Consider adding a @targetName annotation to one of the conflicting definitions
for disambiguation.

darn type erasure…