This is going to be a somewhat long post. Essentially, I've been trying out a new way of structuring my code, and was wondering if something like this has been done before, and what people think of it.
The problem I'm trying to solve is that of *local globals*. These are variables that you want to access from a whole bunch of different places in the code. They kind of belong to an environment your objects live in, but you do not want to make the global because globals are bad: doing so would preclude having more than one environment, and generally make everything (testing, etc.) difficult.
An example of a *local global* would be the language the application is using. Another example would be the acceleration due to gravity in a video game. Another, common in web frameworks, is the HttpRequest: almost everyone wants access to it for various things: picking the correct language, or getting the currently logged in User. Using the HttpRequest as an example I've seen this solved in a variety of ways:
- Pass it around everywhere. This is basically what Play! 2.0 does, as far as I could tell from:
This has the disadvantage of needing to thread the Request all the way through the call stack to the functions who needs it. Sometimes the function which needs the Request is pretty deep in the call stack, and threading the Request through all the function calls just to reach him at the bottom can be rather tedious. Implicits help reduce the pain (no longer need to pass it *in* to functions) but doesn't remove it (still need to declare it in function headers).
- Fake a global. This is what Flask (the python web framework) does:
Essentially, before they run your code, they manually set the global variables to the values you want so you can access them throughout your code. After you're code is run, it is set back to whatever it was before. This clearly works (lots of people use Flask!) but it seems really magic and brittle: having to actively use mutable (!) global (!!) state to avoid having to thread variables through the call stack seems pretty extreme.
The solution I've come up with mirrors how objects contain methods: you often do not need to thread variables through all an objects internal method calls, because these variables are often put into the class scope. They are then accessible from any method within the class without needing to be passed in, but do not preclude having more than one instance of that class.
Essentially, I place all the classes whom i want to have access to this environment in a single big class. Something like:
class Environment{
val localglobal = ...
class A(...){
...
}
class B(...){
...
}
class C(...){
...
}
class D(...){
...
}
}
Doing this, when i create an Environment, all classes and methods within it have access to localglobal directly. Unlike simply making it global/static (or doing the global-mutable-magic that Flask does), it does not preclude creating multiple environments and running them at the same time, e.g. for unit testing.
It still makes unit testing individual classes *inside* the Environment difficult (have to set up entire environment before doing stuff with classes),
just as object-oriented programming makes unit testing individual methods difficult (have to create and initialize object before doing stuff with methods),
but it seems a reasonable compromise short of using global variables (have to set up *everything* before doing stuff!).
It seems like a perfect way of introducing controlled-globalness into the program, giving you some of the benefits (no need to thread variables through functions) and some of the costs (functions are less pure) in a controlled fashion/scope (within the Environment).
However, this only works so simply if everything fits into one file, which is unlikely for programs with many interacting A, B, Cs and Ds (e.g. those complex enough with deep enough call stacks that the benefit in not-having-to-thread-variables-through-methods is worthwhile). If I had C# style partial classes, I could just split it up:
class Environment{
val localglobal = ...
}
// new file
partial class Environment{
class A(...){
...
}
class B(...){
...
}
}
//new file
partial class Environment{
class C(...){
...
}
class D(...){
...
}
}
but alas Scala doesn't have partial classes. However, I managed to get something similar using traits:
class Environment
extends File1
with File2{
val localglobal = ...
}
//new file
trait File1{ env: Environment =>
class A(...){
...
}
}
//new file
trait File2{ env: Environment =>
class C(...){
...
}
}
This is basically identical to the partial class technique using traits, except having to put the env:Environment=> thing on each trait as well as "registering" each trait with the Environment by making the Environment extend it. It seems like I finally have it! A set of "global" variables that are visible to all my classes, don't need to be passed around, but only within a certain scope (the Environment).
This turned out much longer than I thought it would. My questions are:
- Has this been done before? It's a problem that has been bugging me for a long time, ever since I made my first 3D game 8 years ago and first found myself needing to pass accelerationDueToGravity around (back then I copped out and just made it global), and it seems like I have found a solution. If I had this problem, other people must have had it too; the Play! and Flask people certainly seem to have bumped into it
- Is there anything I should watch out for, doing these sort of stunts? It seems really elegant (the environment-class relationship mirrors the whole object-method thing from OO programming) but there are probably some hidden pitfalls I don't see.
- Does this have anything to do with the Cake pattern? I have heard about it a lot, have gone through every link on the front page results from Googling "scala cake pattern", and I still haven't figured it out. I've never done any DI before, which probably is part of the reason I have failed to understand it, but it seems to have a lot of the class-within-trait trait-extended-by-container thing that I am doing here.
Thanks!
-Haoyi