1

I've inherited a Scala project that has to be extended and instead of one monolithic monster it has to be split: some code needs to become a library that's used by all the other components and there's a few applications that may be included at some later point.

object Shared {
  // vals, defs
}

=====

import Shared._

object Utils1 {
  // vals, defs
}

=====

import Shared._

object Utils2 {
  // vals, defs
}

=====

import Shared._
import Utils1._

class Class1(val ...) {
  // stuff
}

=====

import Shared._
import Utils2._

class Class2(val ...) {
  // more stuff
}

etc.

The problem is that Utils1 and Utils2 (and many other objects and classes) use the values from Shared (the singleton) and that Shared now has to be instantiated because one of the key things that happens in Shared is that the application name is set (through SparkConf) as well as reading of configuration files with database connection information.

The idea was to have a multi-project build where I can just pick which component needs to be recompiled and do it. Since the code in Utils is shared by pretty much all applications that currently exist and will come, it's nice to keep it together in one large project, at least so I thought. It's hard to publish to a local repository the code that's common because we have no local repository (yet) and getting permission to have one is difficult.

The Utils singletons are needed because they have to be static (because of Spark and serializability).

When I make Shared a proper class, then all the import statements will become useless and I have to pass an instance around, which means I cannot use singletons, even though I need those. It currently works because there is only a single application, so there really only one instance of Shared. In future, there will still be only one instance of Shared per application but there may be multiple applications/services defined in a single project.

I've looked into implicits, package objects, and dependency injection but I'm not sure that's the right road to go down on. Maybe I'm just not seeing what should be obvious.

Long story short, is there a neat way to give Shared a parameter or achieve what I hope to achieve?

2
  • No, you cannot add parameters to Shared Commented Aug 12, 2016 at 11:00
  • What about passing parameters to Shared's methods when you need? Commented Aug 12, 2016 at 11:02

3 Answers 3

2

Maybe as an option you can create SharedClass class and make Shared object extend it in each app with different constructor params:

  class SharedClass(val param: String) { // vals ...       
  }

  object Shared extends SharedClass("value")
Sign up to request clarification or add additional context in comments.

2 Comments

How would I use Shared in the different objects/classes? Shared would only be set in main of each app and value depends on the application. The utility functions in Utils would have to still obtain an instance and those are singletons themselves.
I mean you can define this object in each app separately. And it still be singleton.
0

One hacky way to do it, is to use a DynamicVariable and make it have a value, when Shared is initialized (i.e., the first time anything that refers to Shared fields or methods is called).

Consider the following:

/* File: Config.scala */

import scala.util.DynamicVariable

object Config {
  val Cfg: DynamicVariable[String] = new DynamicVariable[String](null)
}

/**********************/

/* File: Shared.scala */

import Config._

object Shared {
  final val Str: String = Cfg.value

  def init(): Unit = { }
}

/*********************/

/* File: Utils.scala */

import Shared._

object Utils {
  def Length: Int = Str.length
}

/********************/

/* File: Main.scala */

object Main extends App 
{
  // Make sure `Shared` is initialized with the desired value
  Config.Cfg.withValue("foobar") {
    Shared.init()
  }

  // Now Shared.Str is fixed for the duration of the program
  println(Shared.Str)     // prints: foobar
  println(Utils.Length)   // prints: 6
}

This setup is thread-safe, although not deterministic, of course. The following Main would randomly select one of the 3 strings, and print the selected string and its length 3 times:

import scala.util.Random
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object Main extends App
{
  def random(): Int = Random.nextInt(100)

  def foo(param: String, delay: Int = random()): Future[Unit] = {
    Future {
      Thread.sleep(delay)

      Config.Cfg.withValue(param) {
        Shared.init()
      }

      println(Shared.Str)
      println(Utils.Length)
    }
  }

  foo("foo")
  foo("foobar")
  foo("foobarbaz")

  Thread.sleep(1000)

}

2 Comments

Is it just me but that reeks of global variables? Would you recommend that I just refactor the monster into various class instances and take out stuff from objects that depends on these 'environment' variables?
@Ian Well, yes. (Technically it's more like global constants: the values are set exactly once). But using Shared singleton is already sort of using global state. This answer is just to show one way how it's technically possible, and I've never used something like this in production. (Another way is simply to load values from environment variables or configuration files) Some refactoring may be in order, or you may try to use a dependency injection framework. I haven't worked with Spark, so I'm not sure which solution works and is better.
0

object in Scala has "apply" method which you can use for this purpose. So, your Shared object will be like below

object Shared {
  def apply(param1: String, param2: String) = ???
}

Now each client of Shared can pass different values.

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.