33

How do I create a properly functional configurable object in Scala? I have watched Tony Morris' video on the Reader monad and I'm still unable to connect the dots.

I have a hard-coded list of Client objects:

class Client(name : String, age : Int){ /* etc */}

object Client{
  //Horrible!
  val clients  = List(Client("Bob", 20), Client("Cindy", 30))
}

I want Client.clients to be determined at runtime, with the flexibility of either reading it from a properties file or from a database. In the Java world I'd define an interface, implement the two types of source, and use DI to assign a class variable:

trait ConfigSource { 
  def clients : List[Client]
}

object ConfigFileSource extends ConfigSource {
  override def clients = buildClientsFromProperties(Properties("clients.properties"))  
  //...etc, read properties files 
}

object DatabaseSource extends ConfigSource { /* etc */ }

object Client {
  @Resource("configuration_source") 
  private var config : ConfigSource = _ //Inject it at runtime  

  val clients = config.clients 
} 

This seems like a pretty clean solution to me (not a lot of code, clear intent), but that var does jump out (OTOH, it doesn't seem to me really troublesome, since I know it will be injected once-and-only-once).

What would the Reader monad look like in this situation and, explain it to me like I'm 5, what are its advantages?

Larry OBrien
  • 8,484
  • 1
  • 41
  • 75
  • 1
    `val`s *can* be modified using reflection, so it's possible that your dependency injection library could "inject a val" – gerferra Jun 28 '12 at 22:53
  • 2
    @gerferra what is the point of val modified by reflection, if we have var? – om-nom-nom Jun 28 '12 at 23:06
  • why not make `Client` a class with an argument, so the config can be passed to instances of `Client`? – matt b Jun 28 '12 at 23:47
  • @matt Right, that would be another way to do it, but that would still leave me unclear as to whether/why the `Reader` monad would be preferred. – Larry OBrien Jun 29 '12 at 00:01
  • @om-nom-nom I guess all the normal advantages of `val` vs `var` applies, at least if I don't use reflection myself on that `val`... – gerferra Jun 29 '12 at 02:46
  • 2
    You might find the first half of [Rúnar's NEScala talk](http://www.youtube.com/watch?v=ZasXwtTRkio) to be a bit more approachable. – mergeconflict Jun 29 '12 at 02:49
  • @mergeconflict I'm afraid I still don't get it -- would you take a look at http://www.knowing.net/index.php/2012/07/01/struggling-with-the-benefits-of-the-reader-monad/ and either throw up your hands in despair or continue trying to get it through my thick skull? – Larry OBrien Jul 01 '12 at 19:34

1 Answers1

47

Let's start with a simple, superficial difference between your approach and the Reader approach, which is that you no longer need to hang onto config anywhere at all. Let's say you define the following vaguely clever type synonym:

type Configured[A] = ConfigSource => A

Now, if I ever need a ConfigSource for some function, say a function that gets the n'th client in the list, I can declare that function as "configured":

def nthClient(n: Int): Configured[Client] = {
  config => config.clients(n)
}

So we're essentially pulling a config out of thin air, any time we need one! Smells like dependency injection, right? Now let's say we want the ages of the first, second and third clients in the list (assuming they exist):

def ages: Configured[(Int, Int, Int)] =
  for {
    a0 <- nthClient(0)
    a1 <- nthClient(1)
    a2 <- nthClient(2)
  } yield (a0.age, a1.age, a2.age)

For this, of course, you need some appropriate definition of map and flatMap. I won't get into that here, but will simply say that Scalaz (or Rúnar's awesome NEScala talk, or Tony's which you've seen already) gives you all you need.

The important point here is that the ConfigSource dependency and its so-called injection are mostly hidden. The only "hint" that we can see here is that ages is of type Configured[(Int, Int, Int)] rather than simply (Int, Int, Int). We didn't need to explicitly reference config anywhere.

As an aside, this is the way I almost always like to think about monads: they hide their effect so it's not polluting the flow of your code, while explicitly declaring the effect in the type signature. In other words, you needn't repeat yourself too much: you say "hey, this function deals with effect X" in the function's return type, and don't mess with it any further.

In this example, of course the effect is to read from some fixed environment. Another monadic effect you might be familiar with include error-handling: we can say that Option hides error-handling logic while making the possibility of errors explicit in your method's type. Or, sort of the opposite of reading, the Writer monad hides the thing we're writing to while making its presence explicit in the type system.

Now finally, just as we normally need to bootstrap a DI framework (somewhere outside our usual flow of control, such as in an XML file), we also need to bootstrap this curious monad. Surely we'll have some logical entry point to our code, such as:

def run: Configured[Unit] = // ...

It ends up being pretty simple: since Configured[A] is just a type synonym for the function ConfigSource => A, we can just apply the function to its "environment":

run(ConfigFileSource)
// or
run(DatabaseSource)

Ta-da! So, contrasting with the traditional Java-style DI approach, we don't have any "magic" occurring here. The only magic, as it were, is encapsulated in the definition of our Configured type and the way it behaves as a monad. Most importantly, the type system keeps us honest about which "realm" dependency injection is occurring in: anything with type Configured[...] is in the DI world, and anything without it is not. We simply don't get this in old-school DI, where everything is potentially managed by the magic, so you don't really know which portions of your code are safe to reuse outside of a DI framework (for example, within your unit tests, or in some other project entirely).


update: I wrote up a blog post which explains Reader in greater detail.

mergeconflict
  • 8,156
  • 34
  • 63
  • Just occurred to me, I should also say: **don't worry about making a "configurable object."** A configurable object, really, is just something with constructor parameters. Where do those parameters come from? The caller of the constructor, of course, which will in turn (if I've convinced you to try it) get them from the reader (in this case, the `Configured[...]` environment). It's all about functions calling other functions, not about the guts of your objects. – mergeconflict Jun 29 '12 at 04:32
  • Hmm... So ultimately we still have to refactor all our fn signatures so that they return `Configured[PriorReturnedType]` up to the fn where we choose `run(ConfigFileSource)` or `run(DatabaseSource)`? Why is that superior to passing down the `ConfigSource` as an arg? And I don't follow how "We don't have any "magic" occurring here." We still have to choose `run(ConfigFileSource)` or `run(DatabaseSource)` by a command-line arg or an environment variable or the "magic" of DI, don't we? – Larry OBrien Jun 29 '12 at 22:24
  • 1
    re: "So ultimately we still have to refactor all our fn signatures..." - Some of them, not all. Anything that depends on global configuration will need a `Configured[...]` signature, but your pure functions do not. Again, keeping those two worlds distinctly identifiable is a good thing. – mergeconflict Jun 30 '12 at 03:14
  • 1
    re: "magic" - I can explain how Reader works easily: it's a function, and it takes an argument. In contrast, how does Spring (for example) work? Well, you have some classes in your system that you don't instantiate yourself; you let Spring instantiate them for you. Spring knows that because you've configured your application context. So your application context tells Spring what methods it should call when it constructs your objects. But not really, they have to follow the bean naming convention so that it can derp derp derp you get the point, yes? – mergeconflict Jun 30 '12 at 03:22
  • re: "we still have to choose" - Sure. In either approach, you have to choose which environment you want to use. That aspect is not magic either way. – mergeconflict Jun 30 '12 at 03:25
  • Here's another way of looking at all this, assuming you're not sold yet: **DI is all about global state**, but global state (especially mutable global state) leads to brittle, tightly coupled systems which are difficult to maintain the correctness of over time, as they grow. **Reader provides the convenience of global state** -- some pieces of data are accessible everywhere, without explicitly being passed around as function arguments -- but doesn't suffer the brittleness of global state because the type checker oversees its use. – mergeconflict Jun 30 '12 at 03:58
  • I like this and feel its elegant and good. But I'm still struggling to understand why :) The method def nthClient(n: Int): Configured[Client] is, looked at like a function, Int => (ConfigSource => Client). Passing the ConfigSource in to the method would be def nthClient(n: Int, config: ConfigSource): Client, which is (Int => ConfigSource) => Client. So I'm struggling to see a difference but it certainly feels better. – Channing Walton Jan 17 '13 at 22:51
  • actually the answer is in your ages method - there is no passing around of ConfigSource at all! Doh! – Channing Walton Jan 17 '13 at 22:58
  • Why is the `ages` a `def` ? Why not a `val` ? If I change a property while the the application is running, does the new value get picked up (provided that my application has the ability to scan changes in the conf/properties file) ? Is that the reason why its a `def` ? – Sudheer Aedama Jun 17 '14 at 19:07
  • 1
    Kind of a related question on how Reader Monad DI compares to constructor-params DI, and how to use RM with multiple dependencies/nested method calls: http://stackoverflow.com/questions/29174500/reader-monad-for-dependency-injection-multiple-dependencies-nested-calls – adamw Mar 20 '15 at 19:38