Update: removed user flat map in favor of the Reader story
As Travis already pointed out, to use the Reader template, you need single argument functions. Therefore, to use it for several dependencies, you need to somehow bind all your dependencies into one argument. And here it becomes interesting. The method shown by Travis is the easiest way to do this, but you also need to manually switch environments using .local calls, and if you need several dependencies for the subtrees of your calculation, you need to manually create local environments.
Another way to do this is to have Scala subtyping show this automatically. As long as your dependencies can be mixed, compound things with different or several dependencies work (if you really use a scalar reader, and not if you use flatMap on Function1, as some of the Reader examples do).
Option 1: Cup Cake Sample
One way that allows your dependencies to be able to mix is ββa trimmed cake template. I would call it a cup cake pattern, if I had to call it by name, Dick Wall calls it Parfait (see https://parleys.com/play/53a7d2cde4b0543940d9e55f/chapter28/about ). The idea is not to put everything in the cake, but only put the dependencies in the cake and pass it as a context object, which you can abstract with the reader. Let me apply it to your example:
// business logic class PageFetcher { def fetch(url: String) = Reader((deps: Dep1Component) => Try { ... }) } class ImageExtractor { def extractImages(html: String) = Reader((deps: (Dep2Component with Dep3Component)) => { ... }) } object MyImageFinder { def find(url: String) = for { pageFetcher <- Reader((deps: PageFetcherComponent) => dep.pageFetcher) imageExtractor <- Reader((deps: ImageExtractorComponent) => dep.imageExtractor) htmlTry <- pageFetcher.fetch(url) html <- htmlTry images <- imageExtractor.extractImages(html) } yield images } // I add these 3 useless dependencies here just for demo class Dep1 class Dep2 class Dep3 // cupcake modules trait PageFetcherComponent{ def pageFetcher: PageFetcher } trait ImageExtractorComponent{ def imageExtractor: ImageExtractor } trait Dep1Component{ def dep1: Dep1 } trait Dep2Component { def dep2: Dep2 } trait Dep3Component{ def dep3: Dep3 } object Dependencies extends PageFetcherComponent with ImageExtractorComponent with Dep1Component with Dep2Component with Dep3Component{ val pageFetcher = new PageFetcher val imageExtractor = new ImageExtractor val dep1 = new Dep1 val dep2 = new Dep2 val dep3 = new Dep3 } def main(args: Array[String]) { args.headOption match { case Some(url) => MyImageFinder.find(url)(Dependencies) match { case Success(images) => images.foreach(println) case Failure(err) => println(err.toString) } case _ => println("Please input an url") } }
The image of a cup cake becomes complicated if you have several instances of the same dependencies (several logs, several dbs, etc.) and there is code that you want to selectively use on one or another.
Option 2: Map Indexed By Type
I recently came up with another way to do this using a special data structure that I call type index mapping. It saves the entire boiler plate with a bowl boiler, and this makes it easier to use multiple instances of the same type of dependency (i.e. just wrap them in separate member classes to distinguish them).
/** gets stuff out of a TMap */ def Implicit[V:TTKey] = Reader((c: TMap[V]) => c[V]) // business logic class PageFetcher { def fetch(url: String) = Implicit[Dep1].map{ dep1 => Try { ... }} } class ImageExtractor { def extractImages(html: String) = for{ dep2 <- Implicit[Dep1] dep3 <- Implicit[Dep3] } yield { ... } } object MyImageFinder { def find(url: String) = for { pageFetcher <- Implicit[PageFetcherComponent] imageExtractor <- Implicit[ImageExtractorComponent] htmlTry <- pageFetcher.fetch(url) html <- htmlTry images <- imageExtractor.extractImages(html) } yield images } // I add these 3 useless dependencies here just for demo class Dep1 class Dep2 class Dep3 val Dependencies = TMap(new PageFetcher) ++ TMap(new ImageExtractor) ++ TMap(new Dep1) ++ TMap(new Dep2) ++ TMap(new Dep3) def main(args: Array[String]) { args.headOption match { case Some(url) => MyImageFinder.find(url)(Dependencies) match { case Success(images) => images.foreach(println) case Failure(err) => println(err.toString) } case _ => println("Please input an url") } }
I posted it here https://github.com/cvogt/slick-action/ . Relevant test cases are given here: https://github.com/cvogt/slick-action/blob/master/src/test/scala/org/cvogt/di/TMapTest.scala#L213 This is on maven, but be careful with it use because the code is in the stream and the current implementation is not thread safe in 2.10, only in 2.11 because it relies on TypeTags. I will probably post a version that works for 2.10 and 2.11 at some point.
Addendum Although this solves multi-dependent injection from a monad reader, you still get type errors for htmlTry because you mix Reader / Function1 composition with Try-composition. The solution is to create a wrapper Monad that internally wraps Function1 [TMap [...], Try [...]] and allows you to create them. This requires that you contribute everything to this type of monad, even if something is not needed. Give it a try.