Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions core/shared/src/main/scala/zio/Accessor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package zio

/**
* A simple, macro-less means of creating accessors from Services. Extend
* the companion object with `Accessor[ServiceName]`, then simply call
* `Companion(_.someMethod)`, to return a ZIO effect that requires the
* Service in its environment.
*
* Example:
* {{{
* trait FooService {
* def magicNumber: UIO[Int]
* def castSpell(chant: String): UIO[Boolean]
* }
*
* object FooService extends Accessor[FooService]
*
* val example: ZIO[Has[FooService], Nothing, Unit] =
* for {
* int <- FooService(_.magicNumber)
* bool <- FooService(_.castSpell("Oogabooga!"))
* } yield ()
* }}}
*/
trait Accessor[R] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamgfraser @kitlangton

My only potential concern with this was the name Accessor, which might be pretty common.

The other potential improvement we could make: warn if the user tries to use this on an effect that requires an environment.

trait Accessor[R] {
   def apply[R2, E, A](f: R => ZIO[R2, E, A])(implicit tag: Tag[R], isAny: Accessor.IsAny[R2]): ZIO[Has[R], E, A] = ...
}
object Accessor {
  @implicitNotFound("The methods of your service definition should not use the environment, because this leaks implementation details to clients of the service, and these implementation details should be hidden and free to change based on the specific nature of the implementation. In order to use this accessor, please consider refactoring your service methods so they no longer use ZIO environment.")
  sealed trait IsAny[R]
  implicit val anyIsAny: IsAny[Any] = new IsAny{}

Finally, we should probably extend all companion objects of our own services by this trait.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On trait name:

  • ServiceCompanion
  • ServiceAccessor
  • ???

It could also be useful to think of other utilities which could be placed into the service companion object.

For example:

def makeLayer[R2, E](zio: ZIO[R2, E, R])(implicit tag: Tag[R]): ZLayer[R2, E, Has[R]] = zio.toLayer

I am not sure if this utility pays for itself, but right now, using toLayer sometimes fails because of the invariance of Has, requiring you to widen to the service in the final yield of the for comprehension that builds the effect, e.g.:

(for {
 a <- b 
 c <- d 
} yield ServiceLive(a, c): Service).toLayer

In the companion object, you have access to the type R of the service so you can add utilities that fix it.

This could be changed to:

Service.makeLayer {
  for {
    a <- b 
    c <- d 
  } yield ServiceLive(a, c)
}

If not this one, we should look at opportunities to introduce other utilities that require the service type to be known.

/cc @kitlangton @adamgfraser

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdegoes Yes. If we just want to use it for these accessors then perhaps we could do Accessible which I imagine would be less common but if we want to have other generic utilities related to services then something like ServiceCompanion seems more appropriate.

There is a little risk that we are creating two ways to do things here (e.g. Clock.nanotime versus Clock(_.nanotime)) so could be good to think about our messaging around this. The Clock.nanotime reads really nicely like it is just a method on a static object but does require a little more work to implement the accessors. Is this the "not quite as nice" shortcut for when you don't want to do that? Is it a replacement for the accessors?

Copy link
Member Author

@kitlangton kitlangton May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we could do Accessible

@adamgfraser I like that. Good to have the same name as the macro it's going to replace (in Scala 3).

Is this the "not quite as nice" shortcut for when you don't want to do that

@adamgfraser That would be my argument. The default services are used in lots of places, and the difference in niceness is worth making the accessors. I was just thinking this could be useful when you likely are just going to want some less verbose way of using the accessors in test code/a small smattering of places. If you're making a Service that's generally useful and going to be accessed often, I think it's very much worth it to define the individual accessors. The motivation for this was mainly to provide a replacement for @accessible in Scala 3.

I am not sure if this utility pays for itself, but right now, using toLayer sometimes fails because of the invariance of Has, requiring you to widen to the service in the final yield of the for comprehension that builds the effect, e.g.:

@jdegoes I think that's possible to fix if its broken, though I believe you can currently do zio.toLayer[SuperType] to widen it at the call-site. This works for the toLayer conversions on functions, it'll infer based on the type-signature of the val, or one can pass an type to toLayer.

The other potential improvement we could make: warn if the user tries to use this on an effect that requires an environment

@jdegoes That's great! I was thinking of extending #4823 with environment related warnings. That'll be a great way to teach. We can also add links to our docs from the warnings :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made changes: #5133

def apply[E, A](f: R => ZIO[Any, E, A])(implicit tag: Tag[R]): ZIO[Has[R], E, A] =
ZIO.serviceWith[R](f)
}