Struggling with the benefits of the Reader monad

Still struggling with the benefits of the Reader monad… In the well-liked answer on StackOverflow, mergeconflict says

I can explain how Reader works easily: it’s a function, and it takes an argument.

OK, so I think the structure of the technique seems clear: instead of being dependent on some configurable value, it’s preferred to return a function that takes an argument.

This seems to be asserting that :

def foo : Configuration => Unit = { config : Configuration => bar(config) }

is markedly superior to :

def foo(config : Configuration) : Unit = { bar(config) }

? I’m afraid I don’t see the benefit.

The Reader monad is held out as superior to, in particular, DI frameworks, but it seems that :

In contrast, how does Spring (for example) work? …… “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

This seems like begging the question: the “magic” (or at least Reflection) in Spring is a way to deal with the “you have to choose which environment you want to use,” (choosing a type and setting values) aspect not a way to deal with using that environment (acting on those configured values).

If I understand correctly (and it’s clear that something isn’t clicking with me), the Reader monad doesn’t provide a solution to the “choose which environment” task. I think I’d have to write, in source code, something like


val connectionString = System.getenv(“connection_string”)
//or
val connectionString = args[0]
//or
val connectionString = loadFromPropertiesFile(System.getenv(“properties_file”))
/
/*
…etc any of a million ways, of which @Resource annotations might well be considered the most reasonable
*/

I’m really not willfully trying to drag my feet here. I’ve got a real problem where I have 10KLoC of pretty dense code that has hard-coded throughout it some lists that need to be configurable. I really want to do the technique that has the highest quality.

One thought on “Struggling with the benefits of the Reader monad

  1. Morning! Hopefully your WordPress won’t eat my formatting here, let’s give this a shot…

    This seems to be asserting that :

    def foo : Configuration => Unit = { config : Configuration => bar(config) }

    is markedly superior to :

    def foo(config : Configuration) : Unit = { bar(config) }

    No. In Haskell, for example, there is no distinction between a “function” and a “method” as there is in Scala. There is a mechanical difference between the two in Scala (you need eta expansion to get a function from a method), but that’s not the important part. The important part is that in the functional world, when you want something to be “configurable,” that something should take an argument. You want a “configurable object”? Okay, it should take constructor arguments.

    If I understand correctly (and it’s clear that something isn’t clicking with me), the Reader monad doesn’t provide a solution to the “choose which environment” task. I think I’d have to write, in source code, something like

    val connectionString = System.getenv(“connection_string”)
    //or
    val connectionString = args[0]
    //or
    val connectionString = loadFromPropertiesFile(System.getenv(“properties_file”))
    /
    /*
    …etc any of a million ways, of which @Resource annotations might well be considered the most reasonable
    */

    Yes, the reader approach does not ultimately provide a “bootstrapping” mechanism aside from “run the monad with these arguments.” So yes, you would need to do some shenanigans with property files or whatever. But you seem to be misunderstanding something about @Resource annotations: they don’t provide this for you either! The actual objects that are injected using @Resource annotations are provided through JNDI, which of course gets its configuration data from some global “context.” Who has to eventually configure that? You, of course.

    Same thing with Spring, for example: You might use @Autowired to say where you want things injected, but you’re still responsible for setting up your context somehow. Most typically, you’ll choose at deployment time which context XML to bundle into your WAR file.

    And, while we’re on the topic, it’s also the same thing with Scala’s unusual “cake pattern.” You specify your dependencies in the form of an explicit self type, and you configure and “inject” your dependencies when instantiating an object with whatever mix-ins it requires. How do you choose which versions of those mix-ins you want to inject? Once again, that’s your problem!

    Okay, so “dependency injection” has three parts: where you configure your environment, where you “inject” your environment, and finally where you use your environment. Using the environment once it’s there is easy enough, so let’s ignore that for the moment and just focus on configuration and injection. As I’ve hopefully explained here, configuration is always your problem, no matter how you slice it. You always have to either provide some XML descriptor, pass some explicit arguments, instantiate with some explicit mix-ins or whatever. Injection, on the other hand, may be your problem or may be the framework’s problem. With Spring, JNDI, etc. it’s of course the framework’s problem. Injection occurs mostly-transparently by way of reflection and classloader-fu. With reader, or the cake pattern or whatever, there is no framework so it must be your problem.

    So perhaps the crux of your question is: why would you want to make something your problem that wasn’t already? Well, as long as it’s not too burdensome to implement, you want your code to behave in the most obvious and transparent way possible. You want the ability to debug, for example. Well, it’s quite trivial to debug normal data flow: you see what things were passed to your functions. If you’re getting the wrong things, you know who to blame :) What about with DI frameworks? Shit, have you ever tried debugging a Spring context problem in a large codebase? You often can’t even set a breakpoint to see where things went awry. And aside from the ability to debug, you also want to keep your code as loosely coupled as you can, as I mentioned back over on Stack Overflow. Using something like Reader keeps it explicit which portions on your code depend on the environment, and which portions do not. Naturally you want to isolate that dependent portion, so that you can reuse the rest, and so on.

    Hope this helps…

Comments are closed.