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

Skip to content

Conversation

@SHSongs
Copy link
Contributor

@SHSongs SHSongs commented Apr 19, 2023

/claim #7556

I wrote a draft for Scala 2, and I plan to follow up with Scala 3.

co-authored with @guersam


def makeImpl[A: c.WeakTypeTag](service: c.Expr[ScopedRef[A]]): c.Expr[A] = {

val methods = weakTypeOf[A].decls.collect {
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to proxy val and non-ZIO-returning methods directly, rather than just abort with error.

Imagine:

trait MyService {
  val MyConstant = 4
  def square(x: Int): Int
  def effectful(x: Int): Task[Int] = ZIO.succeed(x * x)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

For this use case, we need an unsafe way to access the underlying service, because ScopedRef#get is an effectful operation.

Possible solutions:

  1. Unwrap ScopedRef#get directly using Runtime
  2. Replace ScopedRef with Ref.Atomic for unsafe get, and let the caller ensure resource safety

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

It seems to me that val makes no sense, because val means the value cannot change.

So we are strictly talking about def which can return different values every time.

Rather than support this, I think maybe your existing implementation makes more sense: just provide a good error message explaining why we can only proxy ZIO-returning methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If val also be hotswapped, I think val also makes sense.

    test("Forwards abstract vals") {
      trait Foo { val bar: UIO[String] }
      val service1: Foo  = new Foo { val bar: UIO[String] = ZIO.succeed("zio1") }
      val service2: Foo = new Foo { val bar: UIO[String] = ZIO.succeed("zio2") }
      for {
        ref  <- ScopedRef.make[Foo](service1)
        proxy = Proxy.generate(ref)
        res1 <- proxy.bar
        _    <- ref.set(ZIO.succeed(service2))
        res2 <- proxy.bar
      } yield assertTrue(res1 == "zio1" && res2 == "zio2")
    }

Copy link
Contributor

Choose a reason for hiding this comment

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

As @SHSongs mentioned, forwarding ZIO vals still makes sense thanks to referential transparency.

Non-ZIO vals can cause trouble as you pointed out, even if it's concrete.

I'd suggest:

  1. Forwards ZIO vals as the test case above shows
  2. Reject non-ZIO abstract vals
  3. Warns about concrete non-ZIO vals that it might cause unexpected behavior

Copy link
Contributor

Choose a reason for hiding this comment

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

I would be inclined to just reject all methods not returning a ZIO value. The layer constructing a service could provide different implementation of the interface when it is reloaded so I don't think it is safe to expose values that aren't ZIO workflows. Exposing all interfaces as ZIO workflows is idiomatic anyway and this is special functionality we are adding to improve ergonomics in common situations so I think it is fine to be opinionated.

@guersam
Copy link
Contributor

guersam commented Apr 22, 2023

Added initial working version for Scala 3.

TODO:

/cc @SHSongs

private object ProxyMacros {

@experimental
def makeImpl[A <: AnyRef : Type](service: Expr[ScopedRef[A]])(using Quotes): Expr[A] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be @experimental due to Symbol.newClass and ClassDef.apply.

import scala.annotation.experimental
import zio.test._

@experimental
Copy link
Contributor

Choose a reason for hiding this comment

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

It breaks cross compilation currently. @SHSongs

@@ -0,0 +1,3 @@
package zio

object Proxy extends ProxyVersionSpecific
Copy link
Contributor

Choose a reason for hiding this comment

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

There will probably be a better place than zio.Proxy.

Copy link
Member

Choose a reason for hiding this comment

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

Let's call it zio.ServiceProxy because it's specifically a type of proxy for service interfaces (also the name Proxy is so generic, it will lead to clashes in other name spaces).

@jdegoes
Copy link
Member

jdegoes commented Apr 26, 2023

@SHSongs @guersam Would love to get this merged in! You look close. Any idea when you will be able to push this across the finish line?

@SHSongs SHSongs marked this pull request as ready for review May 1, 2023 02:32
@SHSongs
Copy link
Contributor Author

SHSongs commented May 1, 2023

@jdegoes
Could you review this PR?

Also, I have a question about the "keep non-ZIO default implementations" test case. Should default implementations be kept for concrete methods, or should they raise an error?"

test("keeps non-ZIO default implementations") {
trait Foo {
def bar: UIO[String]
def qux: String = "quux"
Copy link
Member

Choose a reason for hiding this comment

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

I think this is correct behavior for things like constants. Could be extra strict and require they be final for bulletproof semantics.

@jdegoes jdegoes merged commit 33c5fa1 into zio:series/2.x May 2, 2023
@jdegoes
Copy link
Member

jdegoes commented May 2, 2023

Excellent work! Thank you for this amazing pull request! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants