10

Consider an abstract class defining two properties

abstract class A {
  def a: Int
  def b: Int
  // real A has additional members
}

which is the base class for various case classes such as

case class Foo(a: Int, b: Int) extends A
case class Bar(a: Int, b: Int) extends A
// and many more

Goal: I would finally like to be able to create instances of the aforementioned case classes in two ways, namely

val b1 = Bar(1, 2)
val b2 = Bar(1) has 2
assert(b1 == b2) // must hold

Approach: It therefore seems reasonable to define a helper class that defines has and that allows me to partially construct As

case class PartialA(f: Int => A) {
  def has(b: Int) = f(b)
}

Problem: The current machinery doesn't allow for calls like Bar(1) because this is actually an invocation of Bar.apply(1), that is, of the method apply as defined by the compiler-generated object Bar.

It would be great if I could force the compiler to generate the Bar object as object Bar extends PartialAConstructor, where

abstract class PartialAConstructor{
  def apply(a: Int, b: Int): A // abstract, created when the compiler creates
                               // object Bar
  def apply(a: Int) = PartialA((b: Int) => apply(a, b))
}

However, it doesn't seem to be possible to influence the generation of companion objects of case classes.


Desired properties:

  • Case classes: Foo, Bar etc. should remain case classes because I would like to use the compiler-generated goodies such as structural equality, copy and automatically generated extractors.

  • "Full" structural equality: Defining the case classes as

    case class Bar(a: Int)(val b: Int)
    

    is not an option, because the compiler-generated equals method only considers the first list of arguments, and thus the following would hold erroneously:

    assert(Foo(1)(0) == Foo(1)(10))
    
  • As little code repetition as possible: For example, it is of course possible to define a

    def Bar(a: Int) = PartialA((b: Int) => Bar(a, b))
    

    but that would have to be done for every case class extending A, that, is Foo, Bar etc.

Malte Schwerhoff
  • 12,684
  • 4
  • 41
  • 71

3 Answers3

3

You could heavily rely on currrying (and on the fact that Foo.apply, as any method, will automatically get promoted to a function) and on a little helper to enhance syntax:

object partially {
  def apply[A1,A2,R]( f: (A1, A2) => R ) = f.curried
  def apply[A1,A2,R]( f: (A1, A2) => R, a1: A1 ) = f.curried( a1 )

  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R ) = f.curried
  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R, a1: A1 ) = f.curried( a1 )
  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R, a1: A1, a2: A2 ) = f.curried( a1 )( a2 )


  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R ) = f.curried
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1 ) = f.curried( a1 )
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1, a2: A2 ) = f.curried( a1 )( a2 )
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1, a2: A2, a3: A3 ) = f.curried( a1 )( a2 )( a3 )
  // ... and so on, potentially up to 22 args
}

Then you can do:

scala> val x = partially(Foo)(1)
x: Int => Foo = <function1>
scala> x(2)
res37: Foo = Foo(1,2)

If you really want to use your has method (instead of just directly applying the function), throw in an implicit classes on top of that:

implicit class Func1Ops[-A,+R]( val f: A => R ) extends AnyVal { 
  def has( arg: A ): R = f( arg ) 
}

and now you can do:

scala> val x = partially(Foo)(1)
x: Int => Foo = <function1>

scala> x has 2
res38: Foo = Foo(1,2)
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
2

What's wrong with

val x = Foo(1, _: Int)

You could also add an apply method to the companion, that takes only 1 arg and does the partial application for you.

Other than that, there maybe is a way to do that with type macros, which are not yet released, but you can play around with them in macro paradise.

edit:

To add something to a case classes companion, simply do as you normally would:

case class Foo(x: Int, y: Int)

object Foo {
  def apply(x: Int): (Int => Foo) = Foo(x, _: Int)
}

scala> Foo(1,2)
res3: Foo = Foo(1,2)

scala> Foo(1)
res4: Int => Foo = <function1>

In the apply you could also return your PartialA or whatever you like.

drexin
  • 24,225
  • 4
  • 67
  • 81
  • `val x = Foo(1, _: Int)` doesn't allow me to fix the second argument via a method such as `has` (I am picky, I know). Could you please elaborate on how you would add an `apply` method to the compiler-generated companion object? – Malte Schwerhoff Feb 05 '13 at 13:22
  • If this is the only thing that worries you you can define `implicit class Func1Ops[-A,+R]( val f: A => R ) extends AnyVal { def has( arg: A ): R = f( arg ) }` and then do `x has 2` (in scala 2.10, would be pretty similar in 2.9) – Régis Jean-Gilles Feb 05 '13 at 13:34
  • @drexin Your suggestion is **not** DRY since I would have to do that for `Foo`, `Bar` and every other case class. – Malte Schwerhoff Feb 05 '13 at 13:51
  • @RégisJean-Gilles That should indeed work, thanks. `Foo(1, _: Int) has 2` is slightly less concise than the `Foo(1) has 2` that I was hoping for, but since fixing the first argument (`val f = Foo(1)`) is done significantly less often than fixing the second (`f has 2`), it might not be so bad after all. That the implicit is very generally applicable makes me a bit nervous, however. It would be nice if one could narrow the applicability down to partial instances of `A`. – Malte Schwerhoff Feb 05 '13 at 13:56
  • Well, have a look at my answer for some arguably nicer syntax. As for being nervous about being too general, I don't see a way around that while still respecting the DRY principle. – Régis Jean-Gilles Feb 06 '13 at 07:36
1

Assuming you really want the "has" DSL, and maybe want it extendable, the following works too:

abstract class A {
  def a: Int
  def b: Int
}    

trait PartialHas[T] { 
    self: { def apply(a: Int, b: Int): T } => 
    trait HasWord { def has(b: Int): T }
    def apply(a: Int): HasWord = new HasWord { def has(b: Int): T = apply(a, b) } 
}    

case class Bar(a: Int, b: Int) extends A
object Bar extends PartialHas[Bar]

There may be a way to use a class macro to do away with the explicit companion definition altogether.

Jürgen Strobel
  • 2,200
  • 18
  • 30