Reputation: 281
I've got a class with a collections of Foos we'll call Bar. Foo has a number of number-returning methods that we want to aggregate at the Bar level, like so:
def attribute1(inputs: Map[Int, Double]) =
foos.foldLeft(0d)((sum, foo) => sum + foo.attribute1(inputs(foo.id)))
To aggregate these various attributes, I can have n functions of the form
def attributeN(inputs: Map[Int, Double]) =
foos.foldLeft(0d)((sum, foo) => sum + foo.attributeN(inputs(foo.id)))
However, that's ugly - I hate the fact that the iteration and summation are repeated. I want to abstract that, so I can do something like:
def attribute1(inputs: Map[Int, Double]) = aggregate(Foo.attribute1, inputs)
private def aggregate(f: Double => Double) = foos.foldLeft(0d)((sum, foo) => sum + foo.f(inputs(foo.id)
Of course, that does not work as one cannot reference Foo.attribute1 as a function - . is not a function instance.
I've basically stumbled through various solution, but every one results in code for each aggregation method at least as verbose or complex as what we have with no helper, and I'm left with the duplication of the iteration.
I may be just hoping for too much here, but I am virtually certain there is an elegant way to do this is Scala that is escaping me. So, any of the Scala gurus here who answers - thanks in advance!
Upvotes: 1
Views: 151
Reputation: 3982
Building on @Nikita's answer, if you want to remove a bit more redundancy from your boring methods, you can curry the aggregate
method:
def aggregate(f: (Foo, Double) => Double)(inputs: Map[Int, Double]): Double =
foos.foldLeft(0d)((sum, foo) => sum + f(foo, inputs(foo.id)))
def aggregateAttribute1: Map[Int, Double] => Double =
aggregate(_.attribute1(_))
Upvotes: 0
Reputation: 43309
So basically what you're asking for is a way to address a specific method on multiple instances, right? If so, it's easily solvable:
trait Foo {
def id : Int
def attribute1( x : Double ) : Double
}
def aggregate( f : (Foo, Double) => Double, inputs : Map[Int, Double] ) =
foos.foldLeft(0d)( (sum, foo) => sum + f(foo, inputs(foo.id)) )
def aggregateAttribute1(inputs: Map[Int, Double]) =
aggregate(_.attribute1(_), inputs)
The key to this solution is _.attribute1(_)
which is a sugarred way of writing
(foo, input) => foo.attribute1(input)
Upvotes: 1
Reputation: 25844
I'm not sure I get what you're trying to do, but in scala a number-returning method like this:
def attribute1 = 5
IS a function. Well, sort of... It can be seen as a function with type () => Int
(takes no argument, returns an Integer). You just need to use the omnipresent _
to tell scala to turn attribute1
into a function.
See if this helps as a starting point:
scala> class Foo {
| def attribute1=5
| def attribute2=2
| }
defined class Foo
scala> val foo=new Foo
foo: Foo = Foo@4cbba0bd
// test takes a function of type () => Int and just applies it (note
// the f() followed by () in the right-hand side to say we want to apply f
scala> def test(f: () => Int) = f()
test: (f: () => Int)Int
// the _ after foo.attribute1 tells scala that we want to use
// res2.attribute as a function, not take its value
scala> test(foo.attribute1 _)
res0: Int = 5
Upvotes: 2