score:7
Update: removed custom flatMap in favor of scalaz's Reader
As Travis already pointed out, to use the Reader pattern, you need single argument functions. So in order to use it for multiple dependencies, you somehow need to get all of your dependencies into a single argument. And here it becomes interesting. The way Travis showed is the simplest way to do it, but you also have to manually switch environments using the .local
calls and if you need multiple dependencies for subtrees of your computation, you need to manually build local environments.
Another way to to it is to let Scala's subtyping figure it out auto-magically. As long as your dependencies can be mixed in, composing things with different or multiple dependencies just works (if you actually use scalaz's Reader, not if you use flatMap on Function1 as some of the Reader examples do).
Option 1: Cup cake pattern
One way to allow your dependencies to be able to mixed in is a stripped down cake pattern. I'd call it cup-cake pattern, if I had to give it a name, Dick Wall calls it Parfait (see https://parleys.com/play/53a7d2cde4b0543940d9e55f/chapter28/about ). The idea is instead of putting everything into the cake, only put the dependencies into the cake and pass it through as a context object, which you can abstract over using the reader. Let's 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 cup-cake pattern becomes tricky if you have multiple instances of the same dependencies (multiple loggers, multiple dbs, etc.) and have some code which you want to be able to selectively use on the one or the other.
Option 2: Type-indexed Map
I recently came up with another way to do it using a special data structure I call type-indexed map. It saves all the cup-cake boiler plate and it makes it much easier to use multiple instances of the same type of dependency (i.e. just wrap them in single 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 published it here https://github.com/cvogt/slick-action/ . The corresponding test cases are here: https://github.com/cvogt/slick-action/blob/master/src/test/scala/org/cvogt/di/TMapTest.scala#L213 It's on maven, but be careful when using it, because the code is in flux and the current implementation is not thread-safe in 2.10, only in 2.11, because it relies on TypeTags. I'll probably publish a version that works for 2.10 and 2.11 at some point.
Addendum While this solves multi-dependency injection with the reader monad, you will still get type errors for htmlTry because you are mixing Reader/Function1-composition with Try-composition. The solution is to create a wrapping Monad that internally wraps Function1[TMap[...],Try[...]] and allow composing those. This does require you to stuff everything into this type of monad, even if something wouldn't need a Try.
score:13
If you want to use multiple readers in a for
-comprehension, the argument types will need to be the same, one way or another. One easy way is just to bundle everything up in an environment type (it could just be a tuple), and then use that as the dependency for all your readers.
That throws away a lot of information about fine-grained dependencies in the types, though, and you can also use local
as a kind of map over the input in the for
-comprehension:
case class Foo(i: Int)
case class Bar(s: String)
case class Config(foo: Foo, bar: Bar)
val doSomethingWithFoo: Reader[Foo, String] = Reader(foo => "hello " * foo.i)
val doSomethingWithBar: Reader[Bar, String] = Reader(bar => s"bar is $bar")
val doSomethingWithConfig: Reader[Config, String] = for {
resFoo <- doSomethingWithFoo.local(_.foo)
resBar <- doSomethingWithBar.local(_.bar)
} yield (resFoo, resBar)
Just as map
with a function A => B
can change a Reader[E, A]
to a Reader[E, B]
, local
with E => F
changes Reader[F, A]
to Reader[E, A]
, in this case taking the specific chunk of the environment the reader needs and feeding it in by itself.
Note that there are lots of other combinators on Kleisli
(a more general type—Reader
is just an alias for Kleisli[Id, _, _]
) that are worth reading up on.
Source: stackoverflow.com
Related Query
- How to inject multi dependencies when I use "Reader monad" for dependency injection?
- Reader Monad for Dependency Injection: multiple dependencies, nested calls
- Using Reader Monad for Dependency Injection
- How to pass Messages when I inject MessageApi and use the I18nSupport Trait
- Looking for examples of how to use "@_*" when doing pattern matching in Scala
- How to pushdown limit predicate for Cassandra when you use dataframes?
- How to print dependency jars for use in an environment variable?
- How to use a Monad Transformer when Disjunction is the outermost container?
- How to find out what happens when I use Monoid for Map in scalaz
- How to inject dependencies through Scala Reader from Java code
- How to use provider for dependency injection using guice in playframework
- How can I refactor this to use Monad transformers and for comprehensions?
- Scala: How to use Functors for multi type parameter trait
- How can I get complete stacktraces for exceptions thrown in tests when using sbt and testng?
- What are views for collections and when would you want to use them?
- How to use Scala in IntelliJ IDEA (or: why is it so difficult to get a working IDE for Scala)?
- How do you use scalaz.WriterT for logging in a for expression?
- How can I pass JVM options to SBT to use when running the app or test cases?
- How to use actors for database access and DDD?
- How to use a case classes when hierarchy is needed?
- How to set the number of threads to use for par
- reader writer state monad - how to run this scala code
- SLICK How to define bidirectional one-to-many relationship for use in case class
- How do I use a maven BOM (bill of materials) to manage my dependencies in SBT?
- How do I supply an implicit value for an akka.stream.Materializer when sending a FakeRequest?
- How to escape a string for use in XML/HTML document in Scala?
- How to use just one scala library for maven/eclipse/scala
- How can I use http request headers for content negotiation in a Mashaller?
- Cats - how to use for-comprehension when `Monad` instance in scope?
- How can I use JMH for Scala benchmarks together with sbt?
More Query from same tag
- Specify negative path match for spray routes
- Is `BoundedSourceQueue` from `Source.queue` ok with concurrent producers?
- Array vs In-Memory database
- Scala Play JSON parser throws error on simple key name access
- Retain materialization type when combining Sources
- List[Try[T]] to Try[List[T]] in Scala
- flatting out few hierarchal Options in scala
- Scala tuple question
- Using SBT on a remote node without internet access via SSH
- Curiously recurring template pattern constraints with Scala abstract types
- DSL in scala to write chain comparisons such as a < b <= c
- Scala: Creating a dataframe from a series of lists
- How to setup SBT dependency for scala code that is located in same directory as Build.scala
- String filter using Spark UDF
- How to use widgets to pass dynamic column names in Dataframe select statement
- Garbage collection in the Scala shell
- Scala How do you sort a map that has a String, with numbers in it
- Scala reduceList failing because of type mismatch requiring java.io.Serializable
- How to explode a struct column with a prefix?
- How to traverse column of dataset in spark?
- How to truncate the values of a column of a spark dataframe?
- Is it possible to have a generic logging filter in finagle that can be "inserted anywhere" in a chain of andThens?
- Explain Traverse[List] implementation in scalaz-seven
- Scala typebounds not working with abstract class but works with traits
- Bad Request on AWS ElasticSearch
- Creating Instance of trait
- scala self aware trait
- Creating an extension method for a type that has a constructor, is a type constraint required?
- Reactivemongo getAsTry is not a member of PasswordInfo
- Slick compile query with Set[Int] parameter