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 A
s
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