Dependency Injection

682 views
Skip to first unread message

Billy

unread,
Sep 12, 2011, 7:38:16 AM9/12/11
to scala-debate
A debate started on the scala-language group that I am moving to this
group as it is more fitting here. The matter of DI was discussed, and
I am actively porting the DI portions of Spring to scala to answer a
question of practicality. In this effort, the resulting solution
(which I have dubbed "Recoil") may end up not looking anything like
the original framework. The requirements that spawned this are a
matter of practicality around updating large solutions that have
already been put into production use. My goal is to see if an object-
functional framework can be devised that allows for zero updates to
existing solutions so as to maintain a single codebase within a global
scale enterprise. It is hoped that such a solution will allow for
domain specific rules to be injected in order to meet the needs of
multiple different domains without apriori knowledge of said rules.

Billy

Tony Morris

unread,
Sep 12, 2011, 7:53:50 AM9/12/11
to scala-...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

[moving to scala-debate per agreed request]

I don't recall your constraints so cannot answer your question.

Someone (I forget who) recently wrote (I forget where): "DI is just
socially acceptable global variables." This is mostly true -- I say
mostly because I think the adverb "globally" is redundant and
misleading. That is to say, there is no such thing as a global variable.
All variables are scoped to some context and it is the extent of this
context that is a measure of detriment. This is why you hear people
talking about "keeping their side-effects local." This wishful-thinking
almost never eventuates because side-effects are pervasive. I am
side-tracking here, but going back to the original topic briefly.

In the absence of my awareness of your constraints, I can point out what
it is that most people want when they think they want DI, and in fact,
do not, ever (it is one of many forms of masochism in programming --
bare with me).

First, let us consider a general Scala program and generalise it. This
is just an arbitrary program -- I am trying to make it as convoluted as
possible so that you can go back to a real program and apply the same
reasoning. Importantly, this program is side-effect free at this point.

val a = e1
val b = e2(a)
val c = e3(a, b)
val d = e2(b)

OK, now I am going to generalise it by running the same program, but in
a for-comprehension. We do this by following these rules:
1) Remove the 'val' keyword
2) The = symbol becomes <-
3) We wrap the program in for and yield

I am going to create a data type that simply wraps a value and provides
flatMap and map methods so I can do this:

case class Id[A](i: A) {
def map[B](f: A => B) = Id(f(i))
def flatMap[B](f: A => Id[B]) = f(i)
}

...and since I don't want to explicitly wrap/unwrap my values with Id, I
am going to provide an implicit for in and out:

object Id {
implicit def IdIn[A](a: A) = Id(a)
implicit def IdOut[A](a: Id[A]) = a.i
}

OK, so now let's translate our program:

for {
a <- e1
b <- e2(a)
c <- e3(a, b)
d <- e2(b)
} yield d

Now that you accept that any program can be written this way, let us
step away for a moment and address the idea of DI. There are usually two
variations on DI:
1) The "configuration" (or context) is done and the application must
start by first initialising this context, then the application may run.
The application then reads from the configuration during run-time but
does not modify it. If this order is altered, you end up with a broken
program. A "DI" container attempts to promise you that no such thing
will occur -- this is essentially what the selling point is.

This dependency on explicit execution order is directly anti-thetical to
the functional programming thesis. This is a consequence of there being
a widely-scoped variable that kind-of pretends otherwise.

If you turn your head just a little, you can see this is a somewhat
degenerate notion of what is called "uniqueness typing." I digress.

2) Same as above, however, not only is the application permitted to read
the configuration, but it is also permitted to *write* to it. This means
that the application depends on *more* explicit execution order and the
possibility of bugs increases even more.

Imagine if I said, "you know what, turn all that DI stuff off, we are
going to initialise our values up front and pass them all the way
through the application." You would surely protest, "but that is so
clumsy!" and you'd be right, but only at first glance.

You see, there is a way to pass these values through quite neatly and
no, this is not using Scala's implicit keyword (which is insufficient),
this is something else. OK, so let's first start by thinking about case
1) above where the application only has read access to some context. I
will name this context, "Context", it is a data type that is
somewhere-or-other that we would like to pass through our application --
but no writes to it. I'm sure you can imagine what Context would really
be -- feel free to make it up for the use-case.

So, our values that were once mere values, are now computed as if they
have access to a Context. We can denote this with a data type:

case class ComputedWithContext[A](cx: Context => A)

So we now have "first-class" values computed with a Context, rather than
being mere values. We can now create these by accessing a Context "as if
it were passed" -- that is to say, although we don't yet have a Context,
we may create values that access that Context (when it is eventually
passed) by wrapping a function (or a trait if you prefer).

This is simple and straight-forward enough. But watch this:

case class ComputedWithContext[A](cx: Context => A) {
def map[B](f: A => B): ComputedWithContext[B] = ComputedWithContext(f
compose cx)
def flatMap[B](f: A => ComputedWithContext[B]): ComputedWithContext[B]
= ComputedWithContext(c => f(cx(c)) cx c)
}

We see here that ComputedWithContext happens to have pretty handy map
and flatMap methods. What can we do with them?

OK, so suppose our program above is a little different to the original
in that actually, our expressions (e1, e2 and e3) require a Context, so
each of them becomes become ComputedWithContext[T] where previously they
were just the type T (they may be all different values for T or same --
no matter).

For example, e1 may have been an Int where now it is a
ComputedWithContext[Int] and e2 may have been a String where now it is a
ComputedWithContext[String]. You get the point.

Here is how our program looks:

for {
a <- e1
b <- e2(a)
c <- e3(a, b)
d <- e2(b)
} yield d

This is precisely the same program syntax. The type of this expression
is ComputedWithContext[T] where the type T depends on the value d. In
other words, we may pass a Context in to this value and it gets
"threaded" through our program and our program *doesn't change* if we
write it in this general form. We may "stack these layers" on top of
what started as Id and our program remains unaltered. The "theory" of
doing this is quite involved, mostly because it is kick-arse interesting
and we could talk about it some time, but that's another story!

Importantly, there are no variables here. Not one and not a pretend
value that is actually a variable at application time (which I'm sure
you've been reminded of more than once when using DI).

So, this is how we deal with passing read-only context through our
application:
* without being clumsy by explicitly passing it
* being quite efficient and readable in fact!
* without using variables that leads to program bugs and difficulty
reading and debugging code

How do we deal with read and write values (case 2)? Well, we need a new
different data type for that:

case class WriteWithContext[A](cx: Context => (A, Context))

Notice how this is the same data type as before except the function can
now produce a *new* Context as well as the computed value (paired). This
is to say, we may "modify" the Context as it is threaded through. But
what about map and flatMap, can we write those? Of course:

case class WriteWithContext[A](cx: Context => (A, Context)) {
def map[B](f: A => B): WriteWithContext[B] = WriteWithContext(c => val
(a, cc) = cx(c); (f(a), cc))
def flatMap[B](f: A => WriteWithContext[B]): WriteWithContext[B] =
WriteWithContext(c => { val (a, cc) = cx(c); f(a) cx cc })
}

Don't get too carried away with reading those methods, but just note
that flatMap "threads the Context through whatever the function is,
which may be modifying it."

OK, so now if we suppose that our expressions (e1, e2, e3) actually had
access to the Context, but were also able to "modify" it by returning a
new Context (or just leaving it alone, for which there is library
support of course), then our program would look like this:

for {
a <- e1
b <- e2(a)
c <- e3(a, b)
d <- e2(b)
} yield d

Yep, exactly the same as before. So now we have a value that we can pass
in a Context and it is threaded through the program, potentially
"modifying" the Context as it is threaded through and we get a value and
the resulting Context at the end. We may wish to drop either of these --
in practice, the Context often gets dropped, since it was only need to
compute the value -- and of course, there is library support for that.

So hopefully now you see that DI can be replaced by a superior
programming model, at least for this example, and I promise, for any
example. We just have to come to terms with a few data types and
abstractions and we can kick that baby to the gutter where it belongs.

Hope that helps!


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJObfLOAAoJEPxHMY3rBz0Ps/oH/iXV+fEk9hGdUN0TbdcqR3Fh
QlwVc3VN9El4jLb2yQdopL+93Tov3EYn1beVcC1R7ptI75jtrmRcppPMaJdRUFnN
jeDvqXghac0+evt8zoaK2GqGq1H3R8eG6kdx5pBjf+0PCiJS9RziRQpITb+5Kob2
0I6MSe+beNe7UcVX7HGp9oGVx56CTaieh2R+H6LtGlhwahh//6BBeEyLflIGdc4w
8O8oJwfxT5lnsn2aXsxveB+zkywNyl+dxPEk83o5E3AVIKCvaEnRTKmwd4LsHIQM
D/KVVyaPSKyvziEaVNwAlsukC3LoYg+MBhq9jRAJ65wCEhB+M8oUHNjgnDDbloA=
=J7D1
-----END PGP SIGNATURE-----

Ross Rose

unread,
Sep 12, 2011, 9:44:30 AM9/12/11
to tmo...@tmorris.net, scala-...@googlegroups.com

This approach seems very nice. What if the ComputedWithContext type is externalized and the classes that implement it are dynamically loaded based upon the environment it runs in (i.e. the domain)?

Chris Marshall

unread,
Sep 12, 2011, 11:32:14 AM9/12/11
to tmo...@tmorris.net, scala-...@googlegroups.com
I think this is a fantastic email; however there is one caveat - you imply that the "program" is unchanged, which is true for the semantic structure you have defined. 

It is not true for the program constructs themselves, though (i.e. e1, e2 and e3), where the ComputedWithContext permeates both their input and output. 

What is more, a program which uses many constructs e1, e2 ... eN, all of which are ComputedWithContext, for which, upon fulfilling some new requirement, *a single one* must be changed write to its context too, you must change the input and output arguments to all e1 ... eN.

Perhaps with Haskell this is trivial because of the level of inference going on, but it is a concern in scala, no, that a tiny change might result in hours of development overhead? Are these reasonable observations?

Chris


> Date: Mon, 12 Sep 2011 21:53:50 +1000
> From: tonym...@gmail.com
> To: scala-...@googlegroups.com
> Subject: Re: [scala-debate] Dependency Injection

Razvan Cojocaru

unread,
Sep 12, 2011, 1:11:37 PM9/12/11
to tmo...@tmorris.net, scala-...@googlegroups.com
Nice!! - you should turn this email into a blog (or a series of). Seriously,
I find it very clear and a nice progression... although now, unlike when I
was staring at elephants, I kind of know what you're talking about ;)

If this is how the book is going to be, I can't wait!

Cheers,
Razie

-----Original Message-----
From: scala-...@googlegroups.com [mailto:scala-...@googlegroups.com]
On Behalf Of Tony Morris
Sent: September-12-11 7:54 AM
To: scala-...@googlegroups.com
Subject: Re: [scala-debate] Dependency Injection

Nikolay Artamonov

unread,
Sep 12, 2011, 2:06:23 PM9/12/11
to scala-debate
Thanks, Tony! Was it Reader and Writer monads explained? Can we say
that these monads are FP alternative to DI? By the way, are there
equivalents in Scalaz to haskell's type classes MonadReader/
MonadWriter?

Andreas Scheinert

unread,
Sep 12, 2011, 4:02:18 PM9/12/11
to scala-debate
Hi Nikolay!
That would be the ReaderMonad see here :
http://permalink.gmane.org/gmane.comp.lang.scala.user/36914
And see scalaz examples for more :)

Russ Paielli

unread,
Sep 12, 2011, 4:11:13 PM9/12/11
to tmo...@tmorris.net, scala-...@googlegroups.com
Tony,

That looks interesting. I'm wondering how I can apply it to my problem and what exactly it can do for me. I'm developing an R&D prototype for tactical conflict detection and resolution for air traffic (can be used as an alerting aid for air traffic controllers or can automatically compute resolution maneuvers). It has a couple of dozen switches and parameters.

My first approach was to just create a top-level object called "Config" that contains all the switches and parameters, and access it essentially as a global. That works, but it has a couple of limitations. First, several of my classes depend on one or more values in it, so those classes are not as conveniently reusable as they would otherwise be. That is not necessarily a major issue for me. The more important issue for me is that, to change any switch or parameter, the user needs to recompile. That is not always convenient for someone else running my code (in a simulation, for example).

My current approach eliminates the need to recompile. Instead of using an object, I use a class called Config that has defaults for all switches and parameters and a constructor that accepts command-line arguments to override those defaults if desired. My main program passes the command-line arguments, if any are provided,  to an instance of the Config class, and I use my class called "CommandLine" to parse those arguments. This allows the user to set a few important switches and parameters without recompiling (most of the parameters are rarely varied, so they don't need to be configurable from the command line).

As far as I can tell, the only problem with this approach is that several of my other classes still depend on the resulting Config object. Again, that is not a major issue for me, but it will probably be inconvenient if someone else tries to reuse those classes for another application. Can your approach solve that problem? Or can it perhaps solve another problem that I have overlooked entirely?

In case it helps to clarify the issue, my configuration file follows. Thanks.

--Russ P.

import types._
import Scalar._
import ATMunits._

class Config(com: CommandLine) {

    def this(args: Array[Text]=Array()) = this(new CommandLine(args))

    val trimFlightIDs = com.Bool("trimFlightIDs", false)
        // remove appended Host ID from each flight ID
    val ignoreHostIDs = com.Bool("ignoreHostIDs", true)
        // ignore appended Host IDs when checking for conflicts

    //------------------------------------------------------------------
    // Velocity Filtering
    //------------------------------------------------------------------

    val useTrackFilter = com.Bool("useTrackFilter", false)
        // use first-order TSAFE TrackFilter (otherwise use input velocity)
    val useAltFilter = com.Bool("useAltFilter", true)
        // use first-order TSAFE AltFilter (otherwise use input alt rate)
 
    val alphaTrackFilter = 0.75 // first-order filter parameter (1=no filtering)
    val alphaAltFilter = 1.0 // first-order filter parameter (1=no filtering)

    //------------------------------------------------------------------
    // Altitude Parameters
    //------------------------------------------------------------------

    val cruiseAltTol = 200.1 * ft // cruise altitude tolerance
    val levelRateLimit = 800 * fpm // altitude rate limit for leveloff detection

    val minAltDelay = 6 * sec // min delay to start a climb or descent
    val maxAltDelay = 36 * sec // max delay to start a climb or descent

    val vertAccelNom = 0.1 * gacc // max vertical acceleration or deceleration
    val vertAccelMax = 0.15 * gacc // max vertical acceleration or deceleration

    //------------------------------------------------------------------
    // Flightplan Parameters
    //------------------------------------------------------------------

    val bankAngle = 20 * deg // nominal bank angle for flightplan turn arcs

    val crossTrackTol = 6 * nmi // cross-track tolerance
    val courseDevTol = 30 * deg // course deviation tolerance

    val convergeAngle = 10 * deg // angle of convergence to flightplan route
    val minTurnRadFactor = 0.5 // min turn-radius reduction factor to make next waypoint

    //------------------------------------------------------------------
    // Trajectory Prediction
    //------------------------------------------------------------------

    val predictionPeriod = com.Real("predictionPeriod", 5) * sec
        // limit prediction updates (for ADS-B)
    val predictionTimeStep = com.Real("predictionTimeStep", 6) * sec
        // trajectory prediction time step

    val useWindData = com.Bool("useWindData", true) // use wind data?

    val maxPredTime = 3 * Min // maximum prediction time horizon
    val maxNonCruisePredTime = 2.5 * Min // max prediction time when not in cruise

    val pathPredTimeDR = 2 * Min // dead-reckoning path prediction time limit
    val pathPredTimeShortDR = 1.5 * Min // "short" dead-reckoning path prediction time limit

    val pathPredTimeFP = 3 * Min // flightplan-based path prediction time limit

    val altPredTimeDR = 3 * Min // dead-reckoning altitude prediction time limit
    val altPredTimeFP = 1.5 * Min // flightplan-based alt prediction time limit

    val altPredTimeTransDR = 1.5 * Min // max DR alt pred time in alt transient
    val altPredTimeTransFP = 2.0 * Min // max FP alt pred time in alt transient

    val useTurnDetection = false // 1 for unplanned turn detection
    val pathPredTimeTD = 1.5 * Min // prediction time limit for turn detection

    //------------------------------------------------------------------
    // Conflict Detection
    //------------------------------------------------------------------

    val conflictCheckPeriod = com.Real("ConflictCheckPeriod", 10) * sec
        // limit conflict checks (for ADS-B)

    val HSM = com.Real("HSM", 5) * nmi // horizontal separation minimum (legal)
    val SRT = com.Real("SRT", 1.1) // separation-ratio threshold for alerting

    val conflictHoldTime = 30 * sec // time after last alert at which conflict is dropped

    val useFAR = com.Bool("useFAR", true) // if true, use false alert reduction methods
    val FARbyTime = (1.5 * Min, 0.8) // false-alert reduction by time
    val FARbyAngle = (20 * deg, 1.0) // false-alert reduction by angle
    val FARmaxTime = 1 * Min // time window for second alert of "2 of 3" rule
    val FARminTime = 1 * Min // min time to predicted LoS for "2 of 3" rule

    val checkSafeTurnRange = false // preemptive check of unplanned turns

    val skipTRACONpairs = com.Bool("skipTRACONpairs", false)
        // if true, don't check for conflicts if both flights owned by TRACON

    //------------------------------------------------------------------
    // Critical Leveloff Detection
    //------------------------------------------------------------------

    val useCLdetection = com.Bool("CLdetection", true) // critical leveloff detection
    val CL_SRT = 0.8 // separation-ratio threshold for critical leveloff detection
    val CLtimeLimit = 1.5 * Min // critical leveloff prediction time limit
    val CLaltLimit = 1.5 * kft // overshoot altitude limit

    //------------------------------------------------------------------
    // Aircraft models
    //------------------------------------------------------------------

    val slowAltRateMode = AltRateMode.slow
    val fastAltRateMode = AltRateMode.nom // choose slow, nom, or fast
    val nomAltRateMode = AltRateMode.nom

    val defaultClimbRates = Map[AltRateMode, Scalar](
        AltRateMode.slow -> 1500 * fpm,
        AltRateMode.fast -> 3500 * fpm,
        AltRateMode.nom -> 2000 * fpm)

    val defaultDescentRates = Map[AltRateMode, Scalar](
        AltRateMode.slow -> -1500 * fpm,
        AltRateMode.fast -> -2500 * fpm,
        AltRateMode.nom -> -2000 * fpm)

    val descentRateFactors = (0.8, 1.2)
    }


Jim Powers

unread,
Sep 12, 2011, 8:24:30 PM9/12/11
to Chris Marshall, tmo...@tmorris.net, scala-...@googlegroups.com
On Mon, Sep 12, 2011 at 11:32 AM, Chris Marshall <oxbow...@hotmail.com> wrote:
I think this is a fantastic email; however there is one caveat - you imply that the "program" is unchanged, which is true for the semantic structure you have defined. 

It is not true for the program constructs themselves, though (i.e. e1, e2 and e3), where the ComputedWithContext permeates both their input and output. 

What is more, a program which uses many constructs e1, e2 ... eN, all of which are ComputedWithContext, for which, upon fulfilling some new requirement, *a single one* must be changed write to its context too, you must change the input and output arguments to all e1 ... eN.

Not always.  Suppose that ComputedWithContext took on some new property (like a second input context or something) that was only used by eI and eK but otherwise ignored.  Call that new context ComputedWithExpandedContext One could use an implicit conversions to give all the unmodified e(s) the inputs they are looking for while giving eI and eK the full data set from ComputedWithExpandedContext.
 
Perhaps with Haskell this is trivial because of the level of inference going on, but it is a concern in scala, no, that a tiny change might result in hours of development overhead? Are these reasonable observations?

In Haskell I think you would have to explicitly lift the unmodified e(s) into the new ComputedWithExpandedContext.

Clearly there are cases where pain could be injected like altering the input (read-only) context in incompatible ways.

I'm sure that there may exist even better ways to address this issue, but it will be often the case that you can accommodate small input changes with small code changes.  There is an excellent discussion of this in Wadler's "The Essence of Functional Programming".

-- 
Jim Powers

Ross Rose

unread,
Sep 12, 2011, 9:07:15 PM9/12/11
to tmo...@tmorris.net, scala-...@googlegroups.com

I don't recall your constraints so cannot answer your question.

The devil's in the details. For those not familiar with the constraints around a solution for my argument, here they are as posted previously:

{quote}
Perhaps a use case for DI might be in order. Scenario: developing a (very) large solution in field F for a specific domain D that provides service type T. A new domain D2 is later acquired that falls in F and must provide T. Every aspect about T is the same except for the underlying business rules that apply to the data for D2. Without DI, one would be left maintaining an entirely new branch of T as a separate solution rather than simply injecting those classes which apply the rules for the domain. Now, compound this with new domains being added every year or two (sometimes multiple at one time).
{/quote}

Now, whenever a change to the codebase occurs (within the enterprise), it must be redeployed to all domains to maintain a level environment due to support and maintenance. Thus, recompiling the entire solution for a few altered rules, regardless where they reside in the targeted source, results in all domains needing redeployed. Said deployment can consume up to 20+ man hours _per domain_. DI solves this because the rules for each domain Dx are the only portion that gets compiled and added to any new domains.

From a purist FP stance, DI may be anti-thetical. However, scala is not a pure FP platform and pure FP cannot solve this matter (of recompilation and redeployment) to the best of my knowledge. Thus, I turn to scala's object-functional nature to leverage it as best as possible given the constraints that it may not be a pure FP solution. The results, I hope, will be the best DI container for the foreseeable future that allows for functional solutions to problems in a way that has never before been seen.

Billy

Alois Cochard

unread,
Sep 13, 2011, 2:51:06 AM9/13/11
to scala-debate
--- Sorry I wrote the response on the other thread on scala-language,
I copy my answer here... very sorry for the mistake! ----

Greetings,

I'm actually on a IoC container called "Sindi" and I tried to enforce
application context configuration thru code using a DSL for binding.

It actually look a bit like a mix between guice/spring with a
functional flavour, you can view a full example here:
https://github.com/aloiscochard/sindi/tree/master/sindi-examples/demo

Sindi is totally type-safe, and even better... bindings resolution is
checked at compile time!

Sindi contain a "processor" system aim for doing what Spring did with
bean processing (actually it's used to support Option/Either).

I want to keep Sindi simple and a core IoC component. But I'll start
soon an other framework on top of it called "Sinap" where I want to
add:
- Easy configuration management (and linking to bindings of course)
- Service infrastructure (i.e. lifecycle managament, automatic-proxy)
- Any ceremony you think it could be useful ?
- ...

In a nutshell I want a good toolbox to design application and make
them configurable without the pain I had sometimes with Spring.

Would really like some feedback/ideas/comments, because it looks like
some of you could be really interested by what I'm actually
implementing.

Alois Cochard

Chris Marshall

unread,
Sep 13, 2011, 3:39:18 AM9/13/11
to j...@casapowers.com, tmo...@tmorris.net, scala-...@googlegroups.com
Of course!

scala> implicit def WriteWithContext_is_ComputedWithContext[A](w : WriteWithContext[A]) : ComputedWithContext[A] = ComputedWithContext[A](cx => w.cx(cx)._1)
WriteWithContext_is_ComputedWithContext: [A](w: WriteWithContext[A])ComputedWithContext[A]

scala> implicit def ComputedWithContext_is_WriteWithContext[A](c: ComputedWithContext[A]):  WriteWithContext[A] =  WriteWithContext[A](cx => c.cx(cx) -> cx)
ComputedWithContext_is_WriteWithContext: [A](c: ComputedWithContext[A])WriteWithContext[A]

Chris



From: j...@casapowers.com
Date: Mon, 12 Sep 2011 20:24:30 -0400

Subject: Re: [scala-debate] Dependency Injection

Marius Danciu

unread,
Sep 13, 2011, 9:05:57 AM9/13/11
to tmo...@tmorris.net, scala-...@googlegroups.com
That's how the state monad is born :) .. and the state monad is a beautiful example of DI done right !

Thanks,
Marius

Erik Hetzner

unread,
Sep 13, 2011, 12:22:28 PM9/13/11
to Russ Paielli, scala-...@googlegroups.com
At Mon, 12 Sep 2011 13:11:13 -0700,

Russ Paielli wrote:
>
> Tony,
>
> That looks interesting. I'm wondering how I can apply it to my problem and
> what exactly it can do for me. I'm developing an R&D prototype for tactical
> conflict detection and resolution for air traffic (can be used as an
> alerting aid for air traffic controllers or can automatically compute
> resolution maneuvers). It has a couple of dozen switches and parameters.
>
> My first approach was to just create a top-level object called "Config" that
> contains all the switches and parameters, and access it essentially as a
> global. That works, but it has a couple of limitations. First, several of my
> classes depend on one or more values in it, so those classes are not as
> conveniently reusable as they would otherwise be. That is not necessarily a
> major issue for me. The more important issue for me is that, to change any
> switch or parameter, the user needs to recompile. That is not always
> convenient for someone else running my code (in a simulation, for example).
>
> My current approach eliminates the need to recompile. Instead of using an
> object, I use a class called Config that has defaults for all switches and
> parameters and a constructor that accepts command-line arguments to override
> those defaults if desired. My main program passes the command-line
> arguments, if any are provided, to an instance of the Config class, and I
> use my class called "CommandLine" to parse those arguments. This allows the
> user to set a few important switches and parameters without recompiling
> (most of the parameters are rarely varied, so they don't need to be
> configurable from the command line).

Hi Russ,

You can also use something along the lines of my ssconf [1] to compile
a config file at runtime.

trait MyConfig extends SSConfig {
val alphaTrackFilter = new Value(0.75);
...
}

And your config file looks like this (it will be compiled at runtime):

alphaTrackFilter := 0.80

best, Erik

1. http://bitbucket.org/egh/ssconf

Ross Rose

unread,
Sep 13, 2011, 12:35:59 PM9/13/11
to Erik Hetzner, Russ Paielli, scala-...@googlegroups.com
These are the exact types of examples and ideas I hoped this discussion would kick up...

Billy

> Sent from my free software system <http://fsf.org/>.

Russ P.

unread,
Sep 13, 2011, 7:38:44 PM9/13/11
to scala-debate
Interesting. I didn't even realize that it is possible to compile a
file at runtime in Scala. So if it fails to compile, is it a compile
error or a runtime error? Also, is ssconf considered an internal DSL
or an external DSL (or neither)? Thanks.

--Russ P.

> Hi Russ,
>
> You can also use something along the lines of my ssconf [1] to compile
> a config file at runtime.
>
>   trait MyConfig extends SSConfig {
>     val alphaTrackFilter = new Value(0.75);
>     ...
>   }
>
> And your config file looks like this (it will be compiled at runtime):
>
>   alphaTrackFilter := 0.80
>
> best, Erik
>
> 1.http://bitbucket.org/egh/ssconf

Erik Hetzner

unread,
Sep 14, 2011, 1:26:24 PM9/14/11
to Russ P., scala-debate
At Tue, 13 Sep 2011 16:38:44 -0700 (PDT),

Russ P. wrote:
>
> Interesting. I didn't even realize that it is possible to compile a
> file at runtime in Scala. So if it fails to compile, is it a compile
> error or a runtime error? Also, is ssconf considered an internal DSL
> or an external DSL (or neither)? Thanks.

Hi Russ,

You need to bundle the scala-compiler jar with your runtime, but it’s
the same thing the REPL does, basically.

Here is what a syntax error looks like (it throws a
java.lang.Exception, so you can catch errors when loading your
configs):

/tmp/configurator3129450973169847382scala:4: error: not found: value XXX
traitTest := XXX;

I suppose it would be an internal DSL, because the syntax is
(almost-)pure scala, (I say almost because we append some wrappers
around the text so that config files can look like:

foo := 1;
bar := true;

without the boilerplate object definition wrapped around it.

best, Erik

Tony Morris

unread,
Sep 15, 2011, 1:05:36 AM9/15/11
to scala-...@googlegroups.com
On 09/13/2011 01:32 AM, Chris Marshall wrote:
> I think this is a fantastic email; however there is one caveat - you imply that the "program" is unchanged, which is true for the semantic structure you have defined.
>
> It is not true for the program /constructs /themselves, though (i.e. e1, e2 and e3), where the ComputedWithContext permeates both their input and output.

Correct. By "changed" I just mean the syntax and I only mean to imply that therefore, there is some "common pattern" going on here.


>
> What is more, a program which uses many constructs e1, e2 ... eN, all of which are ComputedWithContext, for which, upon fulfilling some new requirement, *a single one* must be changed write to its context too, you must change the input and output arguments to all e1 ... eN.

I'm not sure what you mean here. We have a high degree of composability, so any new requirements are easily catered for. This is very much unlike traditional DI, where there is a high likelihood of explosion due to the brittle nature of programs that depend on explicit order of execution.


>
> Perhaps with Haskell this is trivial because of the level of inference going on, but it is a concern in scala, no, that a tiny change might result in hours of development overhead? Are these reasonable observations?

Only to the extent that scala's type inference is lacking compared to haskell in general. This is a minor concern compared to getting the overall picture right. In practice, you typically end up putting in more effort to the library code to alleviate burden on the client code and this just becomes a bit trickier with scala, but nothing too concerning.


>
> Chris
>
>
> > Date: Mon, 12 Sep 2011 21:53:50 +1000
> > From: tonym...@gmail.com
> > To: scala-...@googlegroups.com
> > Subject: Re: [scala-debate] Dependency Injection
> >

On 09/12/2011 09:38 PM, Billy wrote:

Tony Morris

unread,
Sep 15, 2011, 1:07:23 AM9/15/11
to scala-...@googlegroups.com
On 09/13/2011 03:11 AM, Razvan Cojocaru wrote:
> Nice!! - you should turn this email into a blog (or a series of). Seriously,
> I find it very clear and a nice progression... although now, unlike when I
> was staring at elephants, I kind of know what you're talking about ;)
>
> If this is how the book is going to be, I can't wait!

Glad it helps mate.

I'm not sure what such a series would look like and I don't want to be "the guy who destroyed the merits of dependency injection", for no other reason than I don't have the time to endure the consequences! I'd rather just share ideas with those who care and I am glad you're one of them :)

Tony Morris

unread,
Sep 15, 2011, 1:10:59 AM9/15/11
to scala-...@googlegroups.com
On 09/13/2011 04:06 AM, Nikolay Artamonov wrote:
> Thanks, Tony! Was it Reader and Writer monads explained? Can we say
> that these monads are FP alternative to DI? By the way, are there
> equivalents in Scalaz to haskell's type classes MonadReader/
> MonadWriter?

You're very close.

There are three "traditional" types of data structure that capture these
ideas:

1) Reader -- reads an environment to compute but does not modify it.
2) Writer -- writes to an environment without any inspection/read of
that environment (think: logging)
3) State -- reads and environment and potentially modifies it.

There is also a fourth that combines all three called RWS or
ReaderWriterState in Scalaz7. This is probably a closer description of
dependency injection itself, but the explanation would have got a bit
confusing. Further, all four of these can (and often are) be "lifted" to
thread yet another environment through.

The two cases I gave were Reader and State.

Scalaz hasn't implemented MonadWriter and MonadReader type-classes for
no other reason than nobody has got to it yet! Going to be that one? :)

Tony Morris

unread,
Sep 15, 2011, 1:13:50 AM9/15/11
to scala-...@googlegroups.com
On 09/13/2011 06:11 AM, Russ Paielli wrote:
> Tony,
>
> That looks interesting. I'm wondering how I can apply it to my problem
> and what exactly it can do for me. I'm developing an R&D prototype for
> tactical conflict detection and resolution for air traffic (can be
> used as an alerting aid for air traffic controllers or can
> automatically compute resolution maneuvers). It has a couple of dozen
> switches and parameters.
>
> My first approach was to just create a top-level object called
> "Config" that contains all the switches and parameters, and access it
> essentially as a global. That works, but it has a couple of
> limitations. First, several of my classes depend on one or more values
> in it, so those classes are not as conveniently reusable as they would
> otherwise be. That is not necessarily a major issue for me. The more
> important issue for me is that, to change any switch or parameter, the
> user needs to recompile. That is not always convenient for someone
> else running my code (in a simulation, for example).

This sounds like case 1) aka "Reader." Happy to help if you can give
specifics, but it's really not much more than building a library around
a function as given.

>
> My current approach eliminates the need to recompile. Instead of using
> an object, I use a class called Config that has defaults for all
> switches and parameters and a constructor that accepts command-line
> arguments to override those defaults if desired. My main program
> passes the command-line arguments, if any are provided, to an
> instance of the Config class, and I use my class called "CommandLine"
> to parse those arguments. This allows the user to set a few important
> switches and parameters without recompiling (most of the parameters
> are rarely varied, so they don't need to be configurable from the
> command line).

The whole "need to recompile" problem can get tricky and is also quite
contentious (makes a great fire-starter). I have my own opinions on
this, which are also shared by a few others, but all I can say for now
is: try to avoid it if you can, and if you cannot, push it way way to
the outside. Hope that helps. Sorry for the dodge.

Tony Morris

unread,
Sep 15, 2011, 1:19:55 AM9/15/11
to Ross Rose, tmo...@tmorris.net, scala-...@googlegroups.com
On 09/13/2011 11:07 AM, Ross Rose wrote:

I don't recall your constraints so cannot answer your question.

The devil's in the details. For those not familiar with the constraints around a solution for my argument, here they are as posted previously:

{quote}
Perhaps a use case for DI might be in order. Scenario: developing a (very) large solution in field F for a specific domain D that provides service type T. A new domain D2 is later acquired that falls in F and must provide T. Every aspect about T is the same except for the underlying business rules that apply to the data for D2. Without DI, one would be left maintaining an entirely new branch of T as a separate solution rather than simply injecting those classes which apply the rules for the domain. Now, compound this with new domains being added every year or two (sometimes multiple at one time).
{/quote}

You're talking about program composition in general here. This is the very thesis of functional programming.



Now, whenever a change to the codebase occurs (within the enterprise), it must be redeployed to all domains to maintain a level environment due to support and maintenance. Thus, recompiling the entire solution for a few altered rules, regardless where they reside in the targeted source, results in all domains needing redeployed. Said deployment can consume up to 20+ man hours _per domain_. DI solves this because the rules for each domain Dx are the only portion that gets compiled and added to any new domains.

Sorry, but DI not only doesn't solve it, but proactively destroys it. I'm hoping my earlier introduction at least sets off an alarm that maybe this is true -- driving it all the way would be a considerably longer essay.



From a purist FP stance, DI may be anti-thetical. However, scala is not a pure FP platform and pure FP cannot solve this matter (of recompilation and redeployment) to the best of my knowledge.

Actually, there is a very useful subset of Scala that is pure functional, but this is quite beside the point.

Pure functional programming doesn't solve this matter -- because it *is* this matter. You're either achieving this goal and doing pure functional programming or you're not achieving this goal and you're not doing functional programming. This is the very thesis of functional programming only stated very briefly. This might help:

1) http://www.cs.utexas.edu/~shmat/courses/cs345/whyfp.pdf
2) http://homepages.inf.ed.ac.uk/wadler/papers/essence/essence.ps

I'd rather not get into the matter of "needing to recompile" because it causes too much flaming without any substance. There is also a great paper on the topic and I cannot google it up -- if someone can remind me, I'd most appreciate it.



Thus, I turn to scala's object-functional nature to leverage it as best as possible given the constraints that it may not be a pure FP solution. The results, I hope, will be the best DI container for the foreseeable future that allows for functional solutions to problems in a way that has never before been seen.

The best DI container is the one that does not exist in my opinion. I hope I've given a reasonable enough introduction into why I hold this opinion.


Billy

Tony Morris

unread,
Sep 15, 2011, 1:24:17 AM9/15/11
to Marius Danciu, tmo...@tmorris.net, scala-...@googlegroups.com
On 09/13/2011 11:05 PM, Marius Danciu wrote:
> That's how the state monad is born :) .. and the state monad is a beautiful example of DI done right !

*high-five*

To give you a taste of how I would progress the argument if required, I'd use ReaderWriterStateT. You might call this "DI done right", but I prefer to just call it "solving problems, no really, for real" or some such.


trait Functor[F[_]] // todo
trait Monad[F[_]] // todo

trait ReaderWriterStateT[R, W, S, F[_], A] {
  val runT: R => S => F[(A, S, W)]

  def map[B](f: A => B)(implicit z: Functor[F]): ReaderWriterStateT[R, W, S, F, B] =
    error("todo")

  def flatMap[B](f: A => ReaderWriterStateT[R, W, S, F, B])(implicit z: Monad[F]): ReaderWriterStateT[R, W, S, F, B] =
    error("todo")
}

case class Id[A](a: A)

object ReaderWriterStateT {
  type ReaderWriterState[R, W, S, A] =
    ReaderWriterStateT[R, W, S, Id, A]

Russ P.

unread,
Sep 16, 2011, 3:07:14 AM9/16/11
to scala-debate
I'm still struggling to understand exactly what this can do for me. I
must be missing something, because the three objectives Tony listed
above seem to me to be easy to achieve if you never need to change
parameter values or if you are allowed to recompile. (I'm assuming
that all parameters and switches are constant for each run but could
change between runs). Tony didn't explicitly state those requirements,
or did I miss them? He said something about the undesirability of
recompiling, but did he say that the parameter values may need to be
changed (between runs)?

If the parameter values never need to change, then the problem is
simple. All you need is a global config object (Tony's "Context"?)
that everything has access to and depends on. As far as I can tell,
that gets you what Tony claims to have achieved above: no clumsy
passing of values, efficient, readable, and no variables.

As I explained in my earlier reply to this message, I'm trying to set
up a read-only conflguration for an application, where the switches
and parameter values are all constant for each run. I would like to

1. be able to flip switches and change parameter values (between runs)
without recompiling

2. keep my configured classes independent of any global configuration
object (excluding their own companion object) so they can be re-used
in another application without modification or recompilation

3. avoid variables

By using my own straightforward methods, I seem to be able to achieve
any two of these three objectives but not all three. Can Tony's method
achieve all three? If so, then I'd say he has something significant.
If not, then it seems to me that his approach is nothing more than an
alternative (and more complicated) way to achieve something that can
be done fairly easily. I'd like to know which it is.

--Russ P.

http://RussP.us

John Nilsson

unread,
Sep 16, 2011, 3:46:46 AM9/16/11
to Russ P., scala-debate
On Fri, Sep 16, 2011 at 9:07 AM, Russ P. <russ.p...@gmail.com> wrote:

1. be able to flip switches and change parameter values (between runs)
without recompiling

2. keep my configured classes independent of any global configuration
object (excluding their own companion object) so they can be re-used
in another application without modification or recompilation

3. avoid variables

By using my own straightforward methods, I seem to be able to achieve
any two of these three objectives but not all three.

In what way does 2 fail? Or the other way, how do you achieve this when dropping 1 or 3 ?

BR,
John

Russ Paielli

unread,
Sep 16, 2011, 4:10:10 AM9/16/11
to John Nilsson, scala-debate
I can use command-line arguments to set switches and parameter values without recompiling. Then I have two options:

1. I can pass those values to the other classes to configure them. But to do that, I need a variable in the receiving class.

2. I can save the command-line arguments (or the resulting config object) in the same object that holds main. But then the other classes need to access that object to read their configuration. This allows me to avoid variables, but then those classes depend on the "main" object. That's not quite what I want.

Of the two, I think option 1 is preferable.

--Russ P.

--
http://RussP.us

Tony Morris

unread,
Sep 16, 2011, 4:11:04 AM9/16/11
to scala-...@googlegroups.com
I think you missed the point of the essay. There are other options.

Russ Paielli

unread,
Sep 16, 2011, 4:28:23 AM9/16/11
to tmo...@tmorris.net, scala-...@googlegroups.com

Maybe so, but I'd still like to know if the method you outlined can satisfy the three requirements I listed above. If so, can you provide a simple example to demonstrate it? It would be useful to me and probably others too. Sorry if I am just dense.

--Russ P.

--
http://RussP.us

John Nilsson

unread,
Sep 16, 2011, 5:42:15 AM9/16/11
to Russ Paielli, scala-debate
I don't think I follow completely. You want to reference some configuration, but you don't want a dependency on it? The mere fact that you are referencing it is a dependency no matter what technique you use to resolve it.

In any case, if you want a more "hidden" reference you can maybe create factories that does "new MyConfiguredClass with TheConfiguration" for you.

Or you can move the dependency from the classes as such and move it to the operation on the class. def aConfigurableOperation(i:Input, cfg: Config)
or even
def aConfigurableOperation(i:Input)(implicit cfg:Config)
or a variation on that theme
implicit def input2configuredOps(i:Input):ConfiguredOpsOn[Input]


BR,
John

nicola...@gmail.com

unread,
Sep 16, 2011, 5:55:34 AM9/16/11
to Russ Paielli, tmo...@tmorris.net, scala-...@googlegroups.com
On Fri, Sep 16, 2011 at 12:46 AM, John Nilsson <jo...@milsson.nu> wrote:
On Fri, Sep 16, 2011 at 9:07 AM, Russ P. <russ.p...@gmail.com> wrote:

1. be able to flip switches and change parameter values (between runs)
without recompiling

2. keep my configured classes independent of any global configuration
object (excluding their own companion object) so they can be re-used
in another application without modification or recompilation

3. avoid variables


Have you tried something like that:

import java.lang.ThreadLocal

class Parameter[A](initial:  A) {
    val tl = new ThreadLocal[A] {
        override def initialValue() : A = {
            initial
        }
      }
    def apply() = tl.get()
    def withValue[B](a : A, p : => B){
       val old = tl.get()
       tl.set(a) 
       try{
           p
         }
       finally{tl.set(old)}
    }
}

With a companion object:

object Parameter {
  def getConfiguration[A](name : String)(implicit m : Manifest[A]) : Option[A] =  None 
    // should read the configuration here
  def apply[A](name : String,  default : => A) : Parameter[A] =
    new Parameter(getConfiguration(name).getOrElse(default))
    
  def apply[A](name : String): Parameter[A] = 
      apply[A](name, error ("Configuration Error. Not found or not valid: " ++ name))
}

It allows to separate the concern of reading config file.
It also allow change locally a parameter, which can be useful. 
And it prevents to modify it.

Best,

Nicolas.


Chris Marshall

unread,
Sep 16, 2011, 6:05:56 AM9/16/11
to russ.p...@gmail.com, scala-...@googlegroups.com
Russ - quite clearly your global-state is not "safe" in the same manner as Tony's example WiteWithContext

The other issue is that, with a global config singleton, you can have no way of knowing whether a given method needs any config access; because it is not expressed through the type system:

  def calculateCollisionProbability(ctx: ComputedWithContext[Context]) : Double

Quite clearly describes what this function needs access to in order to perform its task

Chris


> Date: Fri, 16 Sep 2011 00:07:14 -0700
> Subject: [scala-debate] Re: Dependency Injection
> From: russ.p...@gmail.com

√iktor Ҡlang

unread,
Sep 16, 2011, 6:08:06 AM9/16/11
to nicola...@gmail.com, Russ Paielli, tmo...@tmorris.net, scala-...@googlegroups.com
On Fri, Sep 16, 2011 at 11:55 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
On Fri, Sep 16, 2011 at 12:46 AM, John Nilsson <jo...@milsson.nu> wrote:
On Fri, Sep 16, 2011 at 9:07 AM, Russ P. <russ.p...@gmail.com> wrote:

1. be able to flip switches and change parameter values (between runs)
without recompiling

2. keep my configured classes independent of any global configuration
object (excluding their own companion object) so they can be re-used
in another application without modification or recompilation

3. avoid variables


Have you tried something like that:

import java.lang.ThreadLocal

class Parameter[A](initial:  A) {
    val tl = new ThreadLocal[A] {
        override def initialValue() : A = {
            initial
        }
      }
    def apply() = tl.get()
    def withValue[B](a : A, p : => B){
       val old = tl.get()
       tl.set(a) 
       try{
           p
         }
       finally{tl.set(old)}
    }
}


Not that I don't like inventing wheels, but what's wrong with: http://www.scala-lang.org/api/current/scala/util/DynamicVariable.html
 
With a companion object:

object Parameter {
  def getConfiguration[A](name : String)(implicit m : Manifest[A]) : Option[A] =  None 
    // should read the configuration here
  def apply[A](name : String,  default : => A) : Parameter[A] =
    new Parameter(getConfiguration(name).getOrElse(default))
    
  def apply[A](name : String): Parameter[A] = 
      apply[A](name, error ("Configuration Error. Not found or not valid: " ++ name))
}

It allows to separate the concern of reading config file.
It also allow change locally a parameter, which can be useful. 
And it prevents to modify it.

Best,

Nicolas.





--
Viktor Klang

Akka Tech Lead
Typesafe - Enterprise-Grade Scala from the Experts

Twitter: @viktorklang

Razvan Cojocaru

unread,
Sep 16, 2011, 8:36:00 AM9/16/11
to Russ P., scala-debate
Here's a practical application of this approach versus the static object:
reloading configuration at run-time.

To make sure you have a consistent view of the configuration for the
duration of one process (thread) you need make sure that on a thread, only
one version of that configuration is used. This solution will pass that
specific version to all members.

You could use a ThreadLocal copy, or come up with your own context passed
around, but you're now on a sliperry slope...

What I would really like to see is a monad that can do this across actors,
eh? Assume the steps of processing are undertaken by individual actors not
simple functions that I can chain in a for, but actors chained by
messages... AND, when I see that, I'd like it distributed too :)

Thinking through this problem in depth you'll find that you've just began to
scratch the surface :)

-----Original Message-----
From: scala-...@googlegroups.com [mailto:scala-...@googlegroups.com]
On Behalf Of Russ P.
Sent: September-16-11 3:07 AM
To: scala-debate
Subject: [scala-debate] Re: Dependency Injection

Tony Morris

unread,
Sep 16, 2011, 8:45:50 AM9/16/11
to √iktor Ҡlang, nicola...@gmail.com, Russ Paielli, tmo...@tmorris.net, scala-...@googlegroups.com
On 16/09/11 20:08, √iktor Ҡlang wrote:
>> Have you tried something like that:
>>
>> import java.lang.ThreadLocal
>>
>> class Parameter[A](initial: A) {
>> val tl = new ThreadLocal[A] {
>> override def initialValue() : A = {
>> initial
>> }
>> }
>> def apply() = tl.get()
>> def withValue[B](a : A, p : => B){
>> val old = tl.get()
>> tl.set(a)
>> try{
>> p
>> }
>> finally{tl.set(old)}
>> }
>> }
>>
>>
:(

> Not that I don't like inventing wheels, but what's wrong with:
> http://www.scala-lang.org/api/current/scala/util/DynamicVariable.html
:(

Was I really that unclear?

√iktor Ҡlang

unread,
Sep 16, 2011, 8:49:14 AM9/16/11
to tmo...@tmorris.net, nicola...@gmail.com, Russ Paielli, scala-...@googlegroups.com


2011/9/16 Tony Morris <tonym...@gmail.com>

Was probably me that was unclear since it wasn't a reply to you...
 

--
Tony Morris
http://tmorris.net/


√iktor Ҡlang

unread,
Sep 16, 2011, 8:50:06 AM9/16/11
to Razvan Cojocaru, Russ P., scala-debate
On Fri, Sep 16, 2011 at 2:36 PM, Razvan Cojocaru <p...@razie.com> wrote:
Here's a practical application of this approach versus the static object:
reloading configuration at run-time.

To make sure you have a consistent view of the configuration for the
duration of one process (thread) you need make sure that on a thread, only
one version of that configuration is used. This solution will pass that
specific version to all members.

You could use a ThreadLocal copy, or come up with your own context passed
around, but you're now on a sliperry slope...

What I would really like to see is a monad that can do this across actors,
eh? Assume the steps of processing are undertaken by individual actors not
simple functions that I can chain in a for, but actors chained by
messages... AND, when I see that, I'd like it distributed too :)

Consensus and distributed means PITA
 

Razvan Cojocaru

unread,
Sep 16, 2011, 9:15:17 AM9/16/11
to √iktor Ҡlang, Russ P., scala-debate

Usually that’s the case, but it’s a little bit simplified for this narrow purpose.

 

If the version number V of the configuration required for that logical thread accompanies the messages for that work unit, every node could retrieve the proper V for that part of the work unit, especially if they agree to keep the last few versions for a while and then you need this backed up by some common git as a fallback – it may get hairy to implement fully, but not that hard for a 99% success rate.

Kris Nuttycombe

unread,
Sep 16, 2011, 10:53:59 AM9/16/11
to scala-debate
Oops, forgot reply-all. Again.

2011/9/16 Tony Morris <tonym...@gmail.com>
Hi, Tony,

Unfortunately, I think some of us *really really* need to see an example of the approach you're suggesting. I've seen you talk about it at least half a dozen times in various threads, and have never been able to figure out how to apply the solution you're suggesting. So please, please give an example like Russ requested!

From my perspective, the future of configuration lies in FRP. A reactive model is the only one that will allow you to safely make changes not merely at compile or load time, but at runtime where whole pieces of your system may need to be reloaded based upon a change to configuration. 

Kris 

Nikolay Artamonov

unread,
Sep 16, 2011, 11:32:48 AM9/16/11
to scala-debate
Thanks, Andreas!

On Sep 13, 12:02 am, Andreas Scheinert
<andreas.schein...@googlemail.com> wrote:
> Hi Nikolay!
> That would be the ReaderMonad see here :http://permalink.gmane.org/gmane.comp.lang.scala.user/36914
> And see scalaz examples for more :)

Alex Cruise

unread,
Sep 16, 2011, 1:18:10 PM9/16/11
to Russ P., scala-debate
On Fri, Sep 16, 2011 at 12:07 AM, Russ P. <russ.p...@gmail.com> wrote:
As I explained in my earlier reply to this message, I'm trying to set
up a read-only conflguration for an application, where the switches
and parameter values are all constant for each run. I would like to

1. be able to flip switches and change parameter values (between runs)
without recompiling

2. keep my configured classes independent of any global configuration
object (excluding their own companion object) so they can be re-used
in another application without modification or recompilation

3. avoid variables

By using my own straightforward methods, I seem to be able to achieve
any two of these three objectives but not all three. Can Tony's method
achieve all three? If so, then I'd say he has something significant.
If not, then it seems to me that his approach is nothing more than an
alternative (and more complicated) way to achieve something that can
be done fairly easily. I'd like to know which it is.

How about this?  Only a sketch, haven't attempted to compile; margin too narrow etc.

abstract class ConfigurationItem[T](val value: T)

case class SwallowLading(unladen: Boolean) extends ConfigurationItem[Boolean](unladen)
case class SwallowOrigin(african: Boolean) extends ConfigurationItem[Boolean](african)

trait Configuration extends Iterable[ConfigurationItem[_]] { // Iterable if you care, or don't bother
  implicit def swallowLading: SwallowLading
  implicit def swallowOrigin: SwallowOrigin

  def iterator = List(swallowLading, swallowOrigin).iterator // Not very DRY, it'd sure be nice to do declarations from sequence pattern matches the way we can from tuple pattern matches!
}

final class ServiceThatCaresAboutSwallows(implicit lading: SwallowLading, implicit origin: SwallowOrigin) {
  val unladen: Boolean = lading.value
  val african: Boolean = origin.value

  def doStuff = error("TODO")
}

object TheSystem {
  def config: Configuration = error("TODO")

  import config._

  val service = new ServiceThatCaresAboutSwallows() // Get the implicit values from the configuration, without coupling to the configuration
}

-0xe1a

Russ Paielli

unread,
Sep 16, 2011, 2:24:04 PM9/16/11
to John Nilsson, scala-debate
On Fri, Sep 16, 2011 at 2:42 AM, John Nilsson <jo...@milsson.nu> wrote:
I don't think I follow completely. You want to reference some configuration, but you don't want a dependency on it? The mere fact that you are referencing it is a dependency no matter what technique you use to resolve it.

The idea is that I want the configured classes to be re-usable in another application without any changes or recompilation. So the configured classes should have their own default configuration that can be overridden by main if desired. To reconfigure them for a particular application or run, main should "push" the new configuration to them. They should not "pull" the configuration from main because that makes them dependent on one particular main and therefore degrades re-usability. But main cannot pushes the configuration to them unless their configuration is a variable. This is the dilemma.
 

In any case, if you want a more "hidden" reference you can maybe create factories that does "new MyConfiguredClass with TheConfiguration" for you.

Or you can move the dependency from the classes as such and move it to the operation on the class. def aConfigurableOperation(i:Input, cfg: Config)
or even
def aConfigurableOperation(i:Input)(implicit cfg:Config)
or a variation on that theme
implicit def input2configuredOps(i:Input):ConfiguredOpsOn[Input]


Thanks for the suggestion, but I'm not sure that answers the question of how to change the configuration (between runs) without recompiling.

--Russ
 


--
http://RussP.us

Russ Paielli

unread,
Sep 16, 2011, 4:46:38 PM9/16/11
to Chris Marshall, scala-...@googlegroups.com
On Fri, Sep 16, 2011 at 3:05 AM, Chris Marshall <oxbow...@hotmail.com> wrote:
Russ - quite clearly your global-state is not "safe" in the same manner as Tony's example WiteWithContext


Unfortunately, it's not clear to me.

 
The other issue is that, with a global config singleton, you can have no way of knowing whether a given method needs any config access; because it is not expressed through the type system:

  def calculateCollisionProbability(ctx: ComputedWithContext[Context]) : Double

Quite clearly describes what this function needs access to in order to perform its task


I fail to grasp how that is fundamentally any different than

import Config._

or

val result = f(a, b, Config.someParameter)

where Config is a just a "global" immutable object. Either way, it seems to me that you have a dependence on something that is external to the classes that you are trying to configure. What am I missing?

--Russ P.

--
http://RussP.us

Randall R Schulz

unread,
Sep 17, 2011, 12:07:06 AM9/17/11
to scala-...@googlegroups.com
[ Ah, Tony sets a Reply-To header. Here's my reply for the whole list.]


On Wednesday 14 September 2011, Tony Morris wrote:
> ...


>
> You're talking about program composition in general here. This is the
> very thesis of functional programming.
>

> ...

Is there anything else? Is there anything _not_ subjugated to the goal
of compositionality?

I ask this because it seems that systems like ScalaZ are so utterly,
extremely single-minded in their pursuit of this notion of composition
that every other concern has been sacrificed.


Randall Schulz

Tony Morris

unread,
Sep 17, 2011, 12:09:30 AM9/17/11
to scala-...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Yes. There is concession all the time.

The goal is single-minded devotion to practical application and
sacrificing from the ideal only to the extent necessary, mandated by
external influences rather by personal ignorance (which, if that
personal ignorance is the barrier, it becomes the target).


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJOdB16AAoJEPxHMY3rBz0PNkkIAKT73jkzP1ogwFjYrcgDS/i0
+Hc7G9yYyv+PeqUWvLWqLv0692CR5ApF5iryh6owpTvJtjGJtx0sC9RUcjIVPZtY
x+XcCRYAa6QdomhJdmrcAQg8fJSkOnrjvp2MpeKHYEbWLjvYIPlChNBr+ZKkcE2X
e6eI6SCgLbeloa/dZWrxgDxKkvTs8+97cOMUjK2rK/DbhiO76qZ6hyy0A2VTrXbY
puhqneohGXmKPlPqCVO8kMtcNwdkfooswTdih0IdaQBIGp/GdfnA6/g2JqVB4haQ
VsRad4I8oE8F4Se0yEfbcyhjw9tL/92RdLwlAa7ThTsZ+8mF+lMTWXB0+sm5zHU=
=TwYI
-----END PGP SIGNATURE-----

Marius Danciu

unread,
Sep 17, 2011, 2:37:33 AM9/17/11
to tmo...@tmorris.net, scala-...@googlegroups.com
Neat !

Billy

unread,
Sep 18, 2011, 9:08:42 AM9/18/11
to scala-...@googlegroups.com, tmo...@tmorris.net
"So hopefully now you see that DI can be replaced by a superior
programming model, at least for this example, and I promise, for any
example."

You are totally correct, no need to discuss this any further. I have taken the ConfigReader example in your presentation and separated the Configuration case class out into its own file (.class), added a Configuration object to it, and extended that object with a trait that contains business rules which the case class uses to populate itself with. Here is the Main.scala file:

abstract class ConfigReader[A] {

  def apply(config: Configuration): A

  def map[B](f: A => B): ConfigReader[B] =

    new ConfigReader[B] {

      def apply(c: Configuration) =

        f(ConfigReader.this.apply(c))

    }

  def flatMap[B](f: A => ConfigReader[B]): ConfigReader[B] =

    new ConfigReader[B] {

      def apply(c: Configuration) =

        f(ConfigReader.this.apply(c))(c)

    }

}


object Main {


    def lift4ConfigReader[A, B, C, D, E](f: A => B => C => D => E):

        ConfigReader[A] =>

        ConfigReader[B] =>

        ConfigReader[C] =>

        ConfigReader[D] =>

        ConfigReader[E] =

        a => b => c => d=>

        for{

            aa <- a

            bb <- b

            cc <- c

            dd <- d

        } yield f(aa)(bb)(cc)(dd)


        def main(args: Array[String]) {


          // utility construction

          def configReader[A](k: Configuration => A): ConfigReader[A] =

            new ConfigReader[A] {

              def apply(c: Configuration) = k(c)

          }

          val hostname = configReader(_.hostname)

          val port = configReader(_.port)

          val outfile = configReader(_.outfile)

          val fullpath = configReader(_.fullPath)

          val r = for {

              h <- hostname

              p <- port

              o <- outfile

              f <- fullpath

            } yield "Hello there " + h + ":" + p +

                    "! Want to write to " + o + "?" + " Full path is: " + f

          val conf =

            Configuration()

          println(r(conf))

        }

}


Here is the Configuration.scala file:


trait BusinessRules {

def strToInt(s: String): Int = {

val i: Int = Integer.parseInt(s.toString())

i

}

def intToString(i: Int): String = {

val s: String = "" + i

s

}

}


object Configuration extends BusinessRules {

def getHostname: String = {

"localhost"

}

def port: Int = 80

def getOutfile: String = {

"/etc/hosts"

}


override def toString: String = {

"ftp://" + getHostname + ":" + port + getOutfile

}

}


case class Configuration(

        hostname: String = Configuration.getHostname,

        port: String = Configuration.intToString(Configuration.port),

        outfile: String = Configuration.getOutfile,

        fullPath: String = Configuration.toString()

        )


Russ Paielli

unread,
Sep 18, 2011, 11:00:29 AM9/18/11
to scala-...@googlegroups.com
Does that allow you to change configuration parameter values between runs without recompiling?

If not, then what does all that boilerplate buy for you that you can't get with

object Configuration {


    val Hostname: String = "localhost"

val port: Int = 80

  val getOutfile: String = "/etc/hosts"

    }


Sorry if that's a dumb question.

--Russ P.
--
http://RussP.us

Ross Rose

unread,
Sep 18, 2011, 11:52:30 AM9/18/11
to Russ Paielli, scala-...@googlegroups.com

On Sep 18, 2011, at 10:00, Russ Paielli <russ.p...@gmail.com> wrote:

Does that allow you to change configuration parameter values between runs without recompiling?

If not, then what does all that boilerplate buy for you that you can't get with

object Configuration {


    val Hostname: String = "localhost"

val port: Int = 80

  val getOutfile: String = "/etc/hosts"

    }


Sorry if that's a dumb question.

--Russ P.



It allows me to place the configuration into a .class file external from the functionality of the rest of the solution. The business rules are free to do whatever they need to in order to apply logic as needed. That notion includes potentially reading settings from a file I suppose (which said file could be updated between executions as you mention). My use case is different and would not need that ability (at least at the present), but the functionality should be completely possible. In fact, what the business rules do is completely open ended. The case class becomes the contract ("interface" if you will), and as long as it is adhered to the sky is the limit.

Russ Paielli

unread,
Sep 18, 2011, 3:04:02 PM9/18/11
to Ross Rose, scala-...@googlegroups.com
On Sun, Sep 18, 2011 at 8:52 AM, Ross Rose <rossr...@gmail.com> wrote:

On Sep 18, 2011, at 10:00, Russ Paielli <russ.p...@gmail.com> wrote:

Does that allow you to change configuration parameter values between runs without recompiling?

If not, then what does all that boilerplate buy for you that you can't get with

object Configuration {


    val Hostname: String = "localhost"

val port: Int = 80

  val getOutfile: String = "/etc/hosts"

    }


Sorry if that's a dumb question.

--Russ P.


--
http://RussP.us

It allows me to place the configuration into a .class file external from the functionality of the rest of the solution.

I can do that with my simple Configuration object too.
 
The business rules are free to do whatever they need to in order to apply logic as needed. That notion includes potentially reading settings from a file I suppose (which said file could be updated between executions as you mention). My use case is different and would not need that ability (at least at the present), but the functionality should be completely possible. In fact, what the business rules do is completely open ended. The case class becomes the contract ("interface" if you will), and as long as it is adhered to the sky is the limit.


I'm still wondering what all that boilerplate gets you.

Alec Zorab

unread,
Sep 18, 2011, 3:18:11 PM9/18/11
to Russ Paielli, Ross Rose, scala-...@googlegroups.com
Ah, but with a touch of magic, we can elide the boilerplate!
Consider that the configreader class appears to be something that
takes a Configuration and gives a T...we might even call that a
Configuration => T !
Thus:

def main(args: Array[String]) {

//if only Function1 had a flatMap method...
import scalaz._
import Scalaz._
//and now it does!

val hostname = (_: Configuration).hostname
val port = (_: Configuration).port
val outfile = (_: Configuration).outfile
val fullpath = (_: Configuration).fullPath

val r: Configuration => String = for {


h <- hostname
p <- port
o <- outfile
f <- fullpath
} yield "Hello there " + h + ":" + p + "! Want to write to " + o +
"?" + " Full path is: " + f

val conf = Configuration()

println(r(conf))

Chris Twiner

unread,
Sep 18, 2011, 3:23:05 PM9/18/11
to Russ Paielli, scala-...@googlegroups.com, Ross Rose

Absolutely nothing in that particular case. However, using a reader monad would allow you to separate the source of the configuration from its use. I.e. test vs prod etc. That's possible as your configuration receiving code is separated via the reading functions.

The difference to DI is of course the immutable nature of it.  Also it could mimic scopes etc but still behave in a similar fashion (the for loop managing "scope").

That said, I am not so convinced of its practicablity - there sure is a lot of boiler plate. Accessing a singletons val is far simpler for most apps. DI imo is far worse at infecting your code.

Tony gave a presentation that covered exactly this. Whilst his emails can be difficult to process that presentation was wonderfuly easy to follow. No idea on the link right now though, but an email sent to the lists also gave the resulting code.

On Sep 18, 2011 9:04 PM, "Russ Paielli" <russ.p...@gmail.com> wrote:
> On Sun, Sep 18, 2011 at 8:52 AM, Ross Rose <rossr...@gmail.com> wrote:
>
>>
>> On Sep 18, 2011, at 10:00, Russ Paielli <russ.p...@gmail.com> wrote:
>>
>> Does that allow you to change configuration parameter values between runs
>> without recompiling?
>>
>> If not, then what does all that boilerplate buy for you that you can't get
>> with
>>
>> object Configuration {
>>
>>
>> val Hostname: String = "localhost"
>>
>> val port: Int = 80
>>
>> val getOutfile: String = "/etc/hosts"
>>
>> }
>>
>> Sorry if that's a dumb question.
>>
>> --Russ P.
>>
>>
>> --

Billy

unread,
Sep 18, 2011, 6:38:13 PM9/18/11
to scala-...@googlegroups.com, Russ Paielli, Ross Rose
Sorry for the long winded first answer...

1) the code presented is by no means optimized (nor optimal for most solutions), it is meant to show some of the depth one could take it to. A domain SME could work on the business rules in a trait separately, which could have also resided in its own file. An individual (or individuals) building the solution's framework common to all domains in the enterprise could maintain the case class and object as part of the framework. The object extends the trait to bring it into scope for the case class. The case class becomes the contract with the ConfigReader for the business rules. As long as the case class remains consistent, nothing else need change in the code being configured. The trait is also constrained to provide the functionality that the case class is needing, but how that is done is completely isolated. The object also offers the framework implementers the liberty to "massage" the functionality provided by the SME in the trait. What this shows is one way to achieve separation of concerns without DI and all the headache as Tony tried to enlighten me to (yet took a while). Note: in my use case, "configured" not only means bringing in strings and port numbers and such, it also means how the domain data is manipulated and other functionality, i.e. it could pull in functions proper. The core of the solution (the "framework") is common to all domains.

A real world example is in order to express the requirement for such a thing: think of something like tax laws across the globe. Each area/region/country has its own specifics about how tax matters are computed. The framework should not care about this, it only needs the results from the tax rules to perform its work, but in a consistent manor. Thus, the framework can work globally for all domains when the tax rules are put into different traits specific to each domain. When the framework is deployed into a new domain, that domain's specific trait (and potentially other objects which the trait may use) is deployed with it to tailor the framework to that domain. This results in a single release of the solution globally while domain specific rules are maintained separately by the SME(s) making it manageable. Should the tax rules change for a domain, only its trait would need updated and deployed.

2) the code I used for this was from Tony's (awesome) presentation. It's located on Vimeo, don't have the link handy. I believe you can get to it from his blog.

Billy

unread,
Nov 3, 2012, 7:18:18 PM11/3/12
to scala-...@googlegroups.com, tmo...@tmorris.net, tonym...@gmail.com
I realize this is a somewhat old thread, but wanted to provide an update on my work in this area. I am now using a Reader style monad (thanks to Tony) to "inject" a class into my solutions as determined by external configurations via Spring (ewwww! you may say). The monad lifts Spring beans (which are also written in Scala) into a class via a trait, and allows me to pull in different functionality based upon the domain the solution is ran in. Said beans contain business rules specific to the domain the solution is ran, and they themselves are configured through external properties files (which are lifted into them through a reader style monad via a Spring bean as well). This solves my original constraints, and yet everything is written using val's, and val's alone. Thus, I believe this is the ultimate balance between domain specific constraints and functional programming. To wit: class A requires a class B type to execute domain logic. I use Spring to instantiate a bean for said logic and pull it into any class that needs those rules by extending it with a reader style monad ala a trait. Within the bean, it configures itself through external properties that are also pulled in via a reader style monad by extending another trait. This paradigm holds to the Reader monad pattern while allowing me to configure which "configuration" is lifted in through minor external edits to the Spring application context. "Why the h!@$ would you do this", one may ask? Let me provide a runtime synopsis:

  • Solution is deployed in multiple domains A, B, C, etc.
  • Solution must maintain a single code base to be maintainable
  • Domains A, B, C, etc. have different business rules for the same type of functionality (e.g. tax rules across the globe)

Thus, I solve the problem with this:

  • Place the business rules within compartmentalized classes for each domain A, B, C, etc.
  • All beans that implement specific business rules (i.e. taxation) have a common interface with the same functions (methods)
  • Use Spring to create beans of those classes, and use Spring 3.1.x profiles to switch between development, QA, and production
  • Place configuration items within properties files (i.e. which domain are we executing in, along with any specific values for same such as tax rates)
  • Use a Reader style monad (trait) to pull in (lift) the appropriate domain specific bean based upon properties
  • Execute logic via the trait as though everything was local to the class, all "variables" are implemented via val's and not var's to maintain deterministic behavior

This methodology works identically to Tony's Reader monad (for configuration), yet the "configuration" is actually a class as instantiated through Spring to allow for it to be altered as domain specific.

Let the flaming begin...

Billy

On Monday, September 12, 2011 6:53:50 AM UTC-5, Tony Morris wrote:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 09/12/2011 09:38 PM, Billy wrote:
> A debate started on the scala-language group that I am moving to this
> group as it is more fitting here. The matter of DI was discussed, and
> I am actively porting the DI portions of Spring to scala to answer a
> question of practicality. In this effort, the resulting solution
> (which I have dubbed "Recoil") may end up not looking anything like
> the original framework. The requirements that spawned this are a
> matter of practicality around updating large solutions that have
> already been put into production use. My goal is to see if an object-
> functional framework can be devised that allows for zero updates to
> existing solutions so as to maintain a single codebase within a global
> scale enterprise. It is hoped that such a solution will allow for
> domain specific rules to be injected in order to meet the needs of
> multiple different domains without apriori knowledge of said rules.
>
> Billy

[moving to scala-debate per agreed request]

I don't recall your constraints so cannot answer your question.

Someone (I forget who) recently wrote (I forget where): "DI is just
socially acceptable global variables." This is mostly true -- I say
mostly because I think the adverb "globally" is redundant and
misleading. That is to say, there is no such thing as a global variable.
All variables are scoped to some context and it is the extent of this
context that is a measure of detriment. This is why you hear people
talking about "keeping their side-effects local." This wishful-thinking
almost never eventuates because side-effects are pervasive. I am
side-tracking here, but going back to the original topic briefly.

In the absence of my awareness of your constraints, I can point out what
it is that most people want when they think they want DI, and in fact,
do not, ever (it is one of many forms of masochism in programming --
bare with me).

First, let us consider a general Scala program and generalise it. This
is just an arbitrary program -- I am trying to make it as convoluted as
possible so that you can go back to a real program and apply the same
reasoning. Importantly, this program is side-effect free at this point.

val a = e1
val b = e2(a)
val c = e3(a, b)
val d = e2(b)

OK, now I am going to generalise it by running the same program, but in
a for-comprehension. We do this by following these rules:
1) Remove the 'val' keyword
2) The = symbol becomes <-
3) We wrap the program in for and yield

I am going to create a data type that simply wraps a value and provides
flatMap and map methods so I can do this:

case class Id[A](i: A) {
  def map[B](f: A => B) = Id(f(i))
  def flatMap[B](f: A => Id[B]) = f(i)
}

...and since I don't want to explicitly wrap/unwrap my values with Id, I
am going to provide an implicit for in and out:

object Id {
  implicit def IdIn[A](a: A) = Id(a)
  implicit def IdOut[A](a: Id[A]) = a.i
}

OK, so now let's translate our program:

for {
  a <- e1
  b <- e2(a)
  c <- e3(a, b)
  d <- e2(b)
} yield d

Now that you accept that any program can be written this way, let us
step away for a moment and address the idea of DI. There are usually two
variations on DI:
1) The "configuration" (or context) is done and the application must
start by first initialising this context, then the application may run.
The application then reads from the configuration during run-time but
does not modify it. If this order is altered, you end up with a broken
program. A "DI" container attempts to promise you that no such thing
will occur -- this is essentially what the selling point is.

This dependency on explicit execution order is directly anti-thetical to
the functional programming thesis. This is a consequence of there being
a widely-scoped variable that kind-of pretends otherwise.

If you turn your head just a little, you can see this is a somewhat
degenerate notion of what is called "uniqueness typing." I digress.

2) Same as above, however, not only is the application permitted to read
the configuration, but it is also permitted to *write* to it. This means
that the application depends on *more* explicit execution order and the
possibility of bugs increases even more.

Imagine if I said, "you know what, turn all that DI stuff off, we are
going to initialise our values up front and pass them all the way
through the application." You would surely protest, "but that is so
clumsy!" and you'd be right, but only at first glance.

How do we deal with read and write values (case 2)? Well, we need a new
different data type for that:

case class WriteWithContext[A](cx: Context => (A, Context))

Notice how this is the same data type as before except the function can
now produce a *new* Context as well as the computed value (paired). This
is to say, we may "modify" the Context as it is threaded through. But
what about map and flatMap, can we write those? Of course:

case class WriteWithContext[A](cx: Context => (A, Context)) {
  def map[B](f: A => B): WriteWithContext[B] = WriteWithContext(c => val
(a, cc) = cx(c); (f(a), cc))
  def flatMap[B](f: A => WriteWithContext[B]): WriteWithContext[B] =
WriteWithContext(c => { val (a, cc) = cx(c); f(a) cx cc })
}

Don't get too carried away with reading those methods, but just note
that flatMap "threads the Context through whatever the function is,
which may be modifying it."

OK, so now if we suppose that our expressions (e1, e2, e3) actually had
access to the Context, but were also able to "modify" it by returning a
new Context (or just leaving it alone, for which there is library
support of course), then our program would look like this:

for {
  a <- e1
  b <- e2(a)
  c <- e3(a, b)
  d <- e2(b)
} yield d

Yep, exactly the same as before. So now we have a value that we can pass
in a Context and it is threaded through the program, potentially
"modifying" the Context as it is threaded through and we get a value and
the resulting Context at the end. We may wish to drop either of these --
in practice, the Context often gets dropped, since it was only need to
compute the value -- and of course, there is library support for that.

So hopefully now you see that DI can be replaced by a superior
programming model, at least for this example, and I promise, for any

example. We just have to come to terms with a few data types and
abstractions and we can kick that baby to the gutter where it belongs.

Hope that helps!


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----


Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJObfLOAAoJEPxHMY3rBz0Ps/oH/iXV+fEk9hGdUN0TbdcqR3Fh
QlwVc3VN9El4jLb2yQdopL+93Tov3EYn1beVcC1R7ptI75jtrmRcppPMaJdRUFnN
jeDvqXghac0+evt8zoaK2GqGq1H3R8eG6kdx5pBjf+0PCiJS9RziRQpITb+5Kob2
0I6MSe+beNe7UcVX7HGp9oGVx56CTaieh2R+H6LtGlhwahh//6BBeEyLflIGdc4w
8O8oJwfxT5lnsn2aXsxveB+zkywNyl+dxPEk83o5E3AVIKCvaEnRTKmwd4LsHIQM
D/KVVyaPSKyvziEaVNwAlsukC3LoYg+MBhq9jRAJ65wCEhB+M8oUHNjgnDDbloA=
=J7D1
-----END PGP SIGNATURE-----

Billy

unread,
Nov 3, 2012, 7:40:53 PM11/3/12
to scala-...@googlegroups.com, tmo...@tmorris.net, tonym...@gmail.com

Billy

unread,
Nov 4, 2012, 10:16:05 PM11/4/12
to scala-...@googlegroups.com, tmo...@tmorris.net, tonym...@gmail.com
I updated my blog post (at the bottom of it) with a link to a complete maven project containing the framework that shows example domains.
Reply all
Reply to author
Forward
0 new messages