Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to doing lots of calculations in constructor - scala

I'm learning scala for a new project, aiming for immutability and functional style wherever possible.

One of the objects I'm creating takes a number of inputs in its constructor, then repeatedly applies a large number of calculations to generate the relevant outputs, which are stored as fields on the object.

While the calculations are performed and their results added to a mutable ListBuffer internally, everything else about the object is immutable - once created you can't change any of the input values and running the calculations again would obviously produce the same result.

However, it doesn't seem right to me to have so many calculations in the constructor. The only way around it I can see is to have the calculated values be vars and provide a run method which performs the calculations - but then this method could be called multiple times, which would be pointless.

Is it actually OK style to do a lot in a scala constructor? There's no calls to the DB for example, just internal calculations. Or is there some pattern for this?

Here's the basic idea in very very simple form:

class Foo(val x:Int, val y:Int, calculations:List[Calculation]) {
  val xHistory = new collection.mutable.ListBuffer[Int]()
  val yHistory = new collection.mutable.ListBuffer[Int]()

  calculations.map { calc => calc.perform(this) }.foreach { result => 
    xHistory += result.x 
    yHistory += result.y
  }
}

Basically I want the inputs wrapped in a handy instance of a Foo object so I can pass it to the various calculation strategies (each of which may need a different combination of inputs).

like image 907
Russell Avatar asked Mar 07 '12 12:03

Russell


People also ask

Can you do calculations in a constructor?

At every line in the constructor, one knows exactly which fields are already set and which are not, making it clearly readable. The calculations are encapsuled in side effect free methods, making them easily changeable. Every method clearly shows what fields are needed for the calculation.

Should constructors have side effects?

Avoid introducing any side-effects or subscriptions in the constructor. For those use cases, use componentDidMount() instead.

Should constructors have business logic?

Constructors are not for business logic. If you're lumbered with a legacy constructor like this, discover the steps to follow to transform the class into clean code.

Can a constructor be called more than once?

There can be only one special method with the name constructor in a class. Having more than one occurrence of a constructor method in a class will throw a SyntaxError error.


2 Answers

Work inside the constructor

Usually I do expensive stuff inside the constructor. But note that the comments mention that constructor code might be less optimized by (insert Java implementation here for which this is correct). Also read the next paragraph if you have a multi-threaded application.

Delayed Init

I don't know anything that might be wrong with doing much work inside a constructor.

As noted inside the comments, there may be problems with code running inside the constructor concerning concurrency. Therefor the DelayedInit trait has been introduced in Scala 2.8.0. Problems with of this kind occur for example when working with Swing GUI elements.

The DelayedInit trait provides another tool to customize initialization sequences of classes and objects. If a class or object inherits from this trait, all its initialization code is packed in a closure and forwarded as an argument to a method named delayedInit which is defined as an abstract method in trait DelayedInit.

Implementations of delayedInit have thus full freedom when to execute the initialization code. For instance, Scala’s new App trait stores all initialization sequences in an internal buffer and executes them when the object’s main method is called.

Lazy Constructs

To delay the computations in a different way, you can use the following methods, which also solve the concurrency issue:

  • You can use lazy val members, which will get computed once they are first requested.
  • If you compute a sequence of expensive objects, you might want to use a "lazy" data structure like Stream. This is like a List that computes the next element only on demand. Thus at some point in time, only the initial part of the Stream that has already been accessed has been computed.

Indication to use lazy

Another consideration you might want to do is whether the computed values will get used at all. If they might not be needed, then using the lazy methods I described is the way to go. On the other hand, if you definately access these expensive memebers, in my opinion there is nothing wrong with doing the computations inside a constructor and using lazy members might add unescessary computational overhead.

Note regarding OP example

It's not ok to do dangerous things inside the constructor; like letting references to the partially constructed object escape the constructor (via a this-reference). The example inside the OP does this with calc.perform(this). This "potential bug" cannot be fixed with the following suggestions.

like image 149
ziggystar Avatar answered Nov 13 '22 04:11

ziggystar


What about creating a factory method that performs all necessary computations and returns new immutable instance prepopulated with computed values? You can use companion object to have factory method close actual object.

object Foo {
    def create(input: ...) {
        val output = //long running computations
        new Foo(output)
    }
}

class Foo(val output: ...)

You might want to hide Foo class constructor as suggested by @Nicolas:

class Foo private (val output: ...)

Now you can write:

val foo: Foo = Foo.create(input)
like image 28
Tomasz Nurkiewicz Avatar answered Nov 13 '22 02:11

Tomasz Nurkiewicz