5

I am trying to create an example of a ZIO Module, that has two implementations:

  1. Using YAML with circe-yaml
  2. Using HOCON with pureConfig

My general Interface looks like this:

trait Service[R] {
  def load[T <: Component](ref: CompRef): RIO[R, T]
}

Now my YAML implementation looks like:

def loadYaml[T <: Component: Decoder](ref: CompRef): RIO[Any, T] = {...}

The Decoder is implementation specific.

The problem is now how to delegate from the Service implementation to loadYaml.

I tried the following:

val components: Components.Service[Any] = new Components.Service[Any] {

  implicit val decodeComponent: Decoder[Component] =
      List[Decoder[Component]](
         Decoder[DbConnection].widen,
           ...
        ).reduceLeft(_ or _)

   def load[T <: Component](ref: CompRef): RIO[Any, T] = loadYaml[T] (ref)
}

This gives me:

Error:(62, 20) could not find implicit value for evidence parameter of type io.circe.Decoder[T]
       loadYaml[T] (ref)

Is there a way to achieve this?

I created an example project on Github: zio-comps-module

The idea is described here: Decouple the Program from its Implementation with ZIO modules

pme
  • 14,156
  • 3
  • 52
  • 95
  • One option would be to create a new **typeclass** that abstracts over the `circe.Decoder` as well as the `pureconfig.ConfigReader` so it is only created for a type T if both implicits exists, then I would add that implicit to the general interface so it is passed down to the specific implementation, each implementation will just use the one it requires. – Luis Miguel Mejía Suárez Dec 07 '19 at 14:03
  • @LuisMiguelMejíaSuárez thanks for the suggestion. But the idea is that the interface has no knowledge of its implementation. Actually the next challenge would be to add a `zio-config` implementation. – pme Dec 07 '19 at 14:39
  • Uhm, sorry I know nothing about **ZIO** _(and I have zero intention of doing so)_ So no idea if there are better ways. However, if you need an implicit you need to forward it up all the call stack. I still believe the idea of creating your own typeclass is the correct approach, maybe it relate somehow with that env thing zio always talks about. – Luis Miguel Mejía Suárez Dec 07 '19 at 15:25

2 Answers2

3

Ok, I found a solution. All I had to do was to adjust the load function:

def load[T <: Component](ref: CompRef): RIO[ComponentsEnv, T] = {
  loadConf[Component](ref).map { case c: T => c }
}

First loadConf with the type Component.

Second cast result (Component) the the result type T.

This works but gives you ugly warnings:

[warn] /Users/mpa/dev/Github/pme123/zio-comps-module/hocon/src/pme123/zio/comps/hocon/HoconComps.scala:37:46: abstract type pattern T is unchecked since it is eliminated by erasure
[warn]       loadConf[Component](ref).map { case c: T => c }
[warn]                                              ^
[warn] /Users/mpa/dev/Github/pme123/zio-comps-module/hocon/src/pme123/zio/comps/hocon/HoconComps.scala:37:36: match may not be exhaustive.
[warn] It would fail on the following inputs: DbConnection(_, _, _, _), DbLookup(_, _, _, _), MessageBundle(_, _)
[warn]       loadConf[Component](ref).map { case c: T => c }
[warn]                                    ^
[warn] two warnings found

Update - I found a solution that get rid of the warnings:

After reading the warning unchecked since it is eliminated by erasure for the tenth time, I remembered that this could be solved with adding ClassTag as Context Bound.

The Service looks now

trait Service[R] {
  def load[T <: Component: ClassTag](ref: CompRef): RIO[R, T]
}
pme
  • 14,156
  • 3
  • 52
  • 95
0

If the issues is that on the trait you have

def load[T <: Component]

and in the implementation u need

def loadYaml[T <: Component: Decoder]

then maybe you just need to parametrize Decoder

trait Service[R, D] {
  def load[T <: Component: D](ref: CompRef): RIO[R, T]
}

If there is no relation between the different type of D then you can define it as Any and then have the trait be trait Service[R, +D].

toxicafunk
  • 396
  • 2
  • 7
  • thanks, but it does not work as the ContextBound is related to T which is defined on the function not the class. If you are motivated you can try yourself: https://github.com/pme123/zio-comps-module . Or let me know if you need a Bounty;). – pme Dec 09 '19 at 17:54
  • You want to make `D` higher-kind for this context bound to make sense and for `Decoder` to be a possible `D`: `Service[R, D[_]]`. – Alexey Romanov Dec 09 '19 at 20:41