Alternative to doing a lot of computation in the constructor - scala

I am learning scala for a new project, striving for immutability and functional style where possible.

One of the objects that I create accepts several inputs in its constructor, and then re-applies a large number of calculations to create the corresponding outputs, which are stored as fields on the object.

While the calculations are performed and their results are added to the mutable ListBuffer internally, everything else about the object is immutable - after creating you cannot change any of the input values ​​and repeat the calculations, obviously the result.

However, it seems wrong to me that there are so many calculations in the constructor. The only way I can see is to have calculated var values ​​and provide a run method that does the calculations, but then this method could be called multiple, which would be pointless.

Does OK style really do a lot in scala constructor? There are no calls to the database, for example, only internal calculations. Or is there some kind of template for this?

Here is the basic idea in a very simple way:

 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 need inputs wrapped in a convenient instance of the Foo object, so I can pass it to various calculation strategies (each of which may require a different combination of inputs).

+7
source share
3 answers

Work inside the constructor

I usually do expensive things inside the constructor. But note that in the comments it is mentioned that the constructor code may be less optimized (insert the Java implementation here for which this is correct). Also read the following paragraph if you have a multi-threaded application.

Initialization delay

I don’t know anything that could be wrong if you work a lot inside the constructor.

As noted in the comments, there may be problems with code running inside the constructor regarding concurrency. Therefore, the DelayedInit attribute was introduced in Scala 2.8.0. Such problems arise, for example, when working with Swing GUI elements.

The DelayedInit function provides another tool for configuring the initialization of sequences of classes and objects. If a class or object is inherited from this attribute, all of its initialization code is wrapped in closure and redirection as an argument to a method called delayedInit, which is defined as an abstract method in the value of DelayedInit.

DelayedInit implementations thus have complete freedom in executing the initialization code. For example, Scala's new App stores all initialization sequences in the internal buffer and executes them when the main object method is called.

Lazy designs

To delay calculations in another way, you can use the following methods, which also solve the concurrency problem:

  • You can use lazy val members that will be evaluated after their first query.
  • If you are calculating a sequence of expensive objects, you can use a "lazy" data structure such as Stream . This is similar to a List , which evaluates the next item only on demand. Thus, at some point in time, only the initial part of the Stream was calculated, which was already available.

Indication of the use of lazy

Another consideration you might want to make is whether the values ​​used will be be used. If they may not be needed, then using the lazy methods that I described is the way to go. On the other hand, if you definitely refer to these dear members, in my opinion, there is nothing wrong with doing the calculations inside the constructor, and using lazy members can add unnecessary computational overhead.

Note regarding the OP example

You cannot do dangerous things inside the constructor; just as references to a partially constructed object exit the constructor (via this -reference). The example inside the OP does this with calc.perform(this) . This "potential error" cannot be corrected by the following sentences.

+7
source

How about creating a factory method that does all the necessary calculations and returns a new immutable instance pre-populated with calculated values? You can use a companion object so that the factory method closes the actual object.

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

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

 class Foo private (val output: ...) 

Now you can write:

 val foo: Foo = Foo.create(input) 
+3
source

One possibility is to encapsulate the calculations in a value initializer

 class Foo(arg1:String, arg2:String){ val (x, y, z) = { //tedious calculation using inputs (result1, result2, result:3)} } 

You can make the values ​​lazy if their consumption can be additional, or if it is more reasonable to take the impact of the performance of your calculations on first use, rather than on construction.

+2
source

All Articles