1

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!

1
  • Please try to find a more descriptive title to your question. Commented Sep 19, 2013 at 17:37

3 Answers 3

2

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
Sign up to request clarification or add additional context in comments.

Comments

1

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)

3 Comments

Yes, the question does boil down to "how do you address a specific method on multiple instances", and, heck, I shoudld've figured the answer lie some how with The Underscore. (To loosely paraphrase Homer Simpson: "Ah, The Underscore: the cause of, and solution to, all of life's problems.")
I've translated your code to my object model (which is just a bit more complex than I indicated - the attribute methods on foo take additional arguments) and it works. Color me rather amazed that the Scala compiler is smart enough to figure out that because aggregate takes a function that takes (Foo, Double) it also allows a function of the form Foo.x(Double). I guess this is what the marriage of OP and FP is all about. Heady times we live in...
I guess, if you'll check out Haskell, you'll be blown away by what actually smart compilers can do. Scala's compiler looks like a toy compared to Haskell's GHC.
0

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(_))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.