Make
is an idiomatic version of the thing that is known as dependency injection
but expressed in typeclass form
that makes it usage close to usual Scala code and provides compile-time checks.
build.sbt
:
libraryDependencies += "io.github.dos65" %% "make" % "0.0.2"
Imports:
import make._
import make.syntax._
Speaking roughly, Make
might be treated as a typeclass with the following signature:
trait Make[F[_], A] {
def make: F[A]
}
Where:
F[_]
is an initialization effectA
a target type
Working with it isn't different from how we usually work with typeclasses.
Instance definition examples:
// define instance
case class Foo(a: Int)
object Foo {
// just a pure instance
implicit val make: Make[IO, Foo] = Make.pure(Foo(42))
// or construct it in F[_]
implicit val make: Make[IO, Foo] = Make.eff(IO(Foo(42)))
}
case class Bar(foo: Foo)
object Bar {
// define instance that is is build from dependency
implicit def make(implicit dep: Make[IO, Foo]): Make[IO, Bar] =
dep.map(foo => Bar(foo))
}
case class Baz(foo: Foo, bar: Bar)
object Baz {
// use tuple for several depencies
implicit def make(implicit dep: Make[IO, (Foo, Bar)]): Make[IO, Baz] =
dep.mapN((foo, bar) => Baz(foo, bar))
}
// or use @autoMake annotation to generate the code above
@autoMake
class AutoBaz(foo: Foo, bar: Bar)
Then summon instances that you need and use:
// single Foo
val fooMake: Make[IO, Foo] = Make.of[IO, Foo]
// several instances
val several: Make[IO, (Baz, AutoBaz)] = Make.of[IO, (Baz, AutoBaz)]
// use
import make.syntax._
val fooIO: IO[Foo] = fooMake.make
In case if instance can't be infered it isn't clear what went wrong.
To get a detailed information debugOf
:
val bazMake = Make.of[IO, Baz]
// [error] FromReadme.scala:39:26: could not find implicit value for parameter m: make.Make[cats.effect.IO,example.FromReadme.Baz]
// could not find implicit value for parameter m: make.Make[cats.effect.IO,example.FromReadme.Baz]
// [error] val bazMake = Make.of[IO, Baz]
// [error] ^
// detailed error with debug
import make.enableDebug._
val bazMake = Make.debugOf[IO, Baz]
// [error] FromReadme.scala:40:31: Make for example.FromReadme.Baz not found
// [error] Make instance for example.FromReadme.Baz:
// [error] Failed at example.FromReadme.Baz.make becase of:
// [error] Make instance for (example.FromReadme.Foo, example.FromReadme.Bar):
// [error] Failed at make.MakeTupleInstances.tuple2 becase of:
// [error] Make instance for example.FromReadme.Foo:
// [error] Make instance for example.FromReadme.Foo not found
// [error] val bazMake = Make.debugOf[IO, Baz]
// [error] ^
A
type in Make[F, A]
is invariant.
So, to use interfaces in other instances you need to provide ContraMake
:
trait Foo
@autoMake
class FooImpl(smt: Smth) extends Foo
implicit val fooFromFooImpl = ContraMake.widen[FooImpl, Foo]
It's up to you what F[_]
to use. The only requirement on it is to have cats.Monad
instance.
Resourse[F, ?]
should cover all needs.