Question

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.

Was it helpful?

Solution

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)

OTHER TIPS

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top