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.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With